Future 模式

Future模式是多线程开发中一种常见的设计模式,核心思想是异步调用.

当我们需要调用一个函数方法时,如果这个函数执行很慢,我们就需要等待,但是有时候,可能并不急着要结果,因此,可以先让被调者立即返回.让它在后台慢慢的处理这个请求,对于调用者来说,则可以先处理一些其他任务,在真正需要数据的场合再去尝试获得需要的数据.

对于Future 模式来说,虽然它无法立即给出你需要的数据,但是他会返回一个契约,将来,可以根据这个契约去重新获取你需要的信息.

上面两幅图分别说明了传统的同步方法和Future模式的同步方法.

Future 模式的主要参与者:

JDK中的Future 模式

Future 接口表示一个任务的生命周期,并提供了相应的方法来判断是否已经完成或取消里面的get()方法的行为取决于任务的状态,完成时会返回值或者抛出异常,如果任务没有完成,任务会阻塞直到完成, RunnableFuture 继承了Future和Runnable两个接口,其中,run()方法用于构造真实的数据.它有一个具体的实现类FutureTask, FutureTask 有一个内部类Sync,一些实质性的工作,会委托Sync完成.而Sync 最终会调用Callable接口,完成实际数据的组装工作.

Callable接口类似于Runnable,但是Runnable不会返回结果,并且无法抛出返回结果的异常,并且无法抛出返回结果的异常,而Callable工程更强大一点,被线程执行后,可以返回值,这个返回值可以被future拿到,也就是说,future可以拿到异步执行任务的返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class FutureCallableDemo {
public static void main(String[] args) throws Exception
{ //单纯使用Future接口和Callable接口.
Callable<String> realData =new RealData("a");
//如果申请了FutureTask,即加了下面这句,那么往线程池中必须submit FutureTask类作为Runnable.
// FutureTask<String> future = new FutureTask<>(realData);
//如果没有加上面这句,直接submit Callable到线程池,申请一个Future接收就好.
ExecutorService es = Executors.newFixedThreadPool(1);
// 执行FutureTask
Future<String> future = es.submit(realData); //此句建立callable和future的联系.
//Thread.sleep(300);// 模拟做一些额外的事情
System.out.println(future.get());// 在需要的时候通过get()方法获得需要的数据.
}
static class RealData implements Callable<String>
{
private String para;

public RealData(String para)
{
this.para=para;
}
@Override
public String call() throws Exception {
StringBuffer sb =new StringBuffer();
for(int i=0;i<10;i++)
{
sb.append(para);
Thread.sleep(100);
}
return sb.toString();
}// 上述代码实现了Callable接口,它的call()方法会构造我们需要的真实数据并返回,这里用sleep进行模拟.
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
一个使用Callable和future的读网页的例子,图片另外一个线程读,文字直接主线程读。
public class FutureRender
{
private final ExcutorService excutor = newFixedThreadPool(10); //实例化一个线程池。

void renderPage(CharSequence source) //读取网页
{
final List<ImageInfo> ImageInfos =scanforImageInfo(source);
Callable<List<ImageData> > task =new Callable<>() //主线程处理文字,另一个线程用来处理图片来加快进度。
{ public List<ImageData> call() //重写Callable中的call方法,相当于runnable的run方法。
{
List<ImageData> result =new ArrayList<ImageData>();
for(ImageInfo imageinfo: iamgeinfos)
{
results.add(imageinfo.download());
}
return results;
}
};

Future<List<ImageData> > future = excutor.submit(task); //把任务交给线程池处理,返回值是future类型。
renderText(source);// 读文本
try{
List<ImageData> imageData =future.get(); //获得线程的返回值。
for(ImageData data :imageData)
{
renderImage(data);
}
}
catch(InterruptedException e)
{
Thread.currentThread().interrupt);
future.cancel(true);
}
catch(ExecutionException e)
{
throw launderThrowable(e.getCause());
}
}
}

FutureTask实现了两个接口,Runnable和Future,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值,那么这个组合的使用有什么好处呢?假设有一个很耗时的返回值需要计算,并且这个返回值不是立刻需要的话,那么就可以使用这个组合,用另一个线程去计算返回值,而当前线程在使用这个返回值之前可以做其它的操作,等到需要这个返回值时,再通过Future得到。