考慮這樣一個情況,使用者可能快速翻頁瀏覽文件中,而圖片檔案很大,如此在瀏覽到有圖片的頁數時,就會導致圖片的載入,因而造成使用者瀏覽文件時會有停頓 的現象,所以我們希望在文件開啟之後,仍有一個背景作業持續載入圖片,如此使用者在快速瀏覽頁面時,所造成的停頓可以獲得改善。
Future模式在請求發生時,會先產生一個Future物件給發出請求的客戶,而同時間,真正的目標物件之生成,由一個 新的執行緒持續進行(即 Worker Thread),真正的目標物件生成之後,將之設定至Future之中,而當客戶端真正需要目標物件時, 目標物件也已經準備好,可以讓客戶提取使用。
一個簡單的Java程式片段示範可能像是這樣:
我觉得很多讲Future模式的文章并没有深刻理解Future模式,其实Future模式只是生产者-消费者模型的扩展。经典“生产者-消费者”模型中消息的生产者不关心消费者何时处理完该条消息,也不关心处理结果。Future模式则可以让消息的生产者等待直到消息处理结束,如果需要的话还可以取得处理结果。
用过Java并发包的朋友或许对Future (interface) 已经比较熟悉了,其实Future 本身是一种被广泛运用的并发设计模式,可在很大程度上简化需要数据流同步的并发应用开发。在一些领域语言(如Alice ML )中甚至直接于语法层面支持Future。
这里就以java.util.concurrent.Future 为例简单说一下Future的具体工作方式。Future对象本身可以看作是一个显式的引用,一个对异步处理结果的引用。由于其异步性质,在创建之初,它所引用的对象可能还并不可用(比如尚在运算中,网络传输中或等待中)。这时,得到Future的程序流程如果并不急于使用Future所引用的对象,那么它可以做其它任何想做的事儿,当流程进行到需要Future背后引用的对象时,可能有两种情况:
对于前一种情况,可以通过调用Future.isDone()判断引用的对象是否就绪,并采取不同的处理;而后一种情况则只需调用get()或
get(long
timeout, TimeUnit
unit)通过同步阻塞方式等待对象就绪。实际运行期是阻塞还是立即返回就取决于get()的调用时机和对象就绪的先后了。
简单而言,Future模式可以在连续流程中满足数据驱动的并发需求,既获得了并发执行的性能提升,又不失连续流程的简洁优雅。
但是Futrue模式有个重大缺陷:当消费者工作得不够快的时候,它会阻塞住生产者线程,从而可能导致系统吞吐量的下降。所以不建议在高性能的服务端使用。
java.util.concurrent.Callable与java.util.concurrent.Future类可以协助您完成Future模式。Future模式在请求发生时,会先产生一个Future对象给发出请求的客户。它的作用类似于代理(Proxy)对象,而同时所代理的真正目标对象的生成是由一个新的线程持续进行。真正的目标对象生成之后,将之设置到Future之中,而当客户端真正需要目标对象时,目标对象也已经准备好,可以让客户提取使用。
Callable是一个接口,与Runnable类似,包含一个必须实现的方法,可以启动为让另一个线程来执行。不过Callable工作完成后,可以传回结果对象。Callable接口的定义如下:
Future文档:
http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html
V
- The result type returned by this
Future‘s get methodpublic interface Future<V>
Future<?>
and
return null as a result of the underlying task.
Sample Usage (Note that the following classes are all made-up.)捏造
interface ArchiveSearcher { String search(String target); }
class App {
ExecutorService executor = ...
ArchiveSearcher searcher = ...
void showSearch(final String target)
throws InterruptedException {
Future<String> future
= executor.submit(new Callable<String>() {
public String call() {
return searcher.search(target);
}});
displayOtherThings(); // do other things while searching
try {
displayText(future.get()); // use future
} catch (ExecutionException ex) { cleanup(); return; }
}
}
The FutureTask
class
is an implementation of Future that
implements Runnable, and so may be executed by
an Executor. For example, the above construction
with submit could be replaced by: FutureTask<String> future =
new FutureTask<String>(new Callable<String>() {
public String call() {
return searcher.search(target);
}});
executor.execute(future);
Memory consistency effects: Actions taken by the asynchronous
computation happen-before actions
following the corresponding Future.get()
in another
thread.
FutureTask
, Executor
PrimeCallable.java package onlyfun.caterpillar; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; public class PrimeCallable implements Callable<int[]> { private int max; public PrimeCallable(int max) { this.max = max; } public int[] call() throws Exception { int[] prime = new int[max+1]; List<Integer> list = new ArrayList<Integer>(); for(int i = 2; i <= max; i++) prime[i] = 1; for(int i = 2; i*i <= max; i++) { // 这里可以改进 if(prime[i] == 1) { for(int j = 2*i; j <= max; j++) { if(j % i == 0) prime[j] = 0; } } } for(int i = 2; i < max; i++) { if(prime[i] == 1) { list.add(i); } } int[] p = new int[list.size()]; for(int i = 0; i < p.length; i++) { p[i] = list.get(i).intValue(); } return p; } }
程序中的求质数方法是很简单的,但效率不好,这里只是为了示范方便,才使用简单的求质数方法,要更有效率地求质数
假设现在求质数的需求是在启动PrimeCallable后的几秒之后,则可以使用Future来获得Callable执行的结果,从而在未来的时间点获得结果
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class FutureDemo { public static void main(String[] args) { Callable<int[]> primeCallable = new PrimeCallable(1000); FutureTask<int[]> primeTask = new FutureTask<int[]>(primeCallable); Thread t = new Thread(primeTask); t.start(); try { // 假设现在做其他事情 Thread.sleep(5000); // 回来看看质数找好了吗 if (primeTask.isDone()) { int[] primes = primeTask.get(); for (int prime : primes) { System.out.print(prime + " "); } System.out.println(); } } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
java.util.concurrent.FutureTask是一个代理,真正执行找质数功能的是Callable对象。使用另一个线程启动FutureTask,之后就可以做其他的事了。等到某个时间点,用isDone()观察任务是否完成,如果完成了,就可以获得结果。一个执行结果
,显示所有找到的质数.
除了Future外,其它比较常见的并发设计模式还包括“回调驱动(多线程环境下)”、“消息/事件驱动(Actor模型中)”等。
回调是最常见的异步并发模式,它有即时性高、接口设计简单等有点。但相对于Future,其缺点也非常明显。首先,多线程环境下的回调一般是在触发回调的模块线程中执行的,这就意味着编写回调方法时通常必须考虑线程互斥问题;其次,回调方式接口的提供者在本模块的线程中执行用户应用的回调也是相对不安全的,因为你无法确定它会花费多长时间或出现什么异常,从而可能间接导致本模块的即时性和可靠性受影响;再者,使用回调接口不利于顺序流程的开发,因为回调方法的执行是孤立的,要与正常流程汇合是比较困难的。因此回调接口适合于在回调中只需要完成简单任务,并且不必与其它流程汇合的场景。
上述这些回调模式的缺点恰恰正是Future的长项。由于Future的使用是将异步的数据驱动天然的融入顺序流程中,因此你完全不必考虑线程互斥问题,Future甚至可以在单线程的程序模型(例如协程)中实现(参见下文将要提到的“Lazy Future”)。另一方面,提供Future接口的模块完全不必担心像回调接口那样的可靠性问题和可能对本模块的即时性影响。
另一类常见的并发设计模式是“消息(事件)驱动”,它一般运用在Actor模型中:服务请求者向服务提供者发送消息,然后继续进行后续不依赖服务处理结果的任务,在需要依赖结果前终止当前流程并记录状态;在等到回应消息后根据记录的状态触发后续流程。这种基于状态机的并发控制虽然比回调更适合于有延续性的顺序流程,但开发者却不得不将连续流程按照异步服务的调用切断为多个按状态区分的子流程,这样又人为的增加了开发的复杂性。运用Future模式可以避免这个问题,不必为了异步调用而打碎连续的流程。但是有一点应当特别注意:Future.get()方法可能会阻塞线程的执行,所以它通常无法直接融入常规的Actor模型中。(基于协程的Actor模型可以较好的解决这个冲突)
Future的灵活性还体现在其同步和异步的自由取舍,开发者可以根据流程的需要自由决定是否需要等待[Future.isDone()],何时等待[Future.get()],等待多久[Future.get(timeout)]。比如可以根据数据是否就绪而决定要不要借这个空档完成点其它任务,这对于实现“异步分支预测”机制是相当方便的。
除了上面提到的基础形态之外,Future还有丰富的衍生变化,这里就列举几个常见的。
与一般的Future不同,Lazy Future在创建之初不会主动开始准备引用的对象,而是等到请求对象时才开始相应的工作。因此,Lazy Future本身并不是为了实现并发,而是以节约不必要的运算资源为出发点,效果上与Lambda/Closure类似。例如设计某些API时,你可能需要返回一组信息,而其中某些信息的计算可能会耗费可观的资源。但调用者不一定都关心所有的这些信息,因此将那些需要耗费较多资源的对象以Lazy Future的形式提供,可以在调用者不需要用到特定的信息时节省资源。
另外Lazy Future也可以用于避免过早的获取或锁定资源而产生的不必要的互斥。
Promise可以看作是Future的一个特殊分支,常见的Future一般是由服务调用者直接触发异步处理流程,比如调用服务时立即触发处理或 Lazy Future的取值时触发处理。但Promise则用于显式表示那些异步流程并不直接由服务调用者触发的情景。例如Future接口的定时控制,其异步流程不是由调用者,而是由系统时钟触发,再比如淘宝的分布式订阅框架提供的Future式订阅接口,其等待数据的可用性不是由订阅者决定,而在于发布者何时发布或更新数据。因此,相对于标准的Future,Promise接口一般会多出一个set()或fulfill()接口。
常规的Future是一次性的,也就是说当你获得了异步的处理结果后,Future对象本身就失去意义了。但经过特殊设计的Future也可以实现复用,这对于可多次变更的数据显得非常有用。例如前面提到的淘宝分布式订阅框架所提供的Future式接口,它允许多次调用waitNext()方法(相当于Future.get()),每次调用时是否阻塞取决于在上次调用后是否又有数据发布,如果尚无更新,则阻塞直到下一次的数据发布。这样设计的好处是,接口的使用者可以在其任何合适的时机,或者直接简单的在独立的线程中通过一个无限循环响应订阅数据的变化,同时还可兼顾其它定时任务,甚至同时等待多个Future。简化的例子如下:
for (;;) { schedule = getNextScheduledTaskTime(); while(schedule > now()) { try { data = subscription.waitNext(schedule - now()); processData(data); } catch(Exception e) {...} } doScheduledTask(); }
future pattern实现:http://computerdragon.blog.51cto.com/6235984/1206497
原文:http://www.cnblogs.com/youxin/p/3587081.html