在上一篇日志中,并行执行下的多线程协作完成查找操作里,我让每一个查找线程实现了Callable接口,从主线程中也看到,使用了Future<Integer>泛型接口来接收线程的返回结果。想了一下,觉得有必要补上一篇日志,总结下自己对Future模式的学习。
使用Runnable还是Callable?
通常我们创建线程都是继承或实现Runnable接口,使用Callable接口和Runnable接口相似,不同之处在于,Runnable接口没有返回值,因为看Runnable接口中,只有一个抽象方法public void run()可以看出,run()方法没有参数传入,返回类型是void,即线程执行完后,不能返回结果。实现Runnable接口的线程如果想要知道执行结果,可能需要通过修改共享变量或者线程间通信的方式告知。而Callable接口是允许有返回值的,先来看看它的源码:
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
可以看到,Callable接口的传入参数类型V,接口里面只有一个方法call(),不过它不是一个抽象方法,而是带返回类型V的方法。也就是说,如果用Callable接口来封装任务,创建线程,则可以获得任务的返回值。
Callable接口使用场景
你可能会想,什么样的场景下需要用到这带返回类型线程?假设一个线程计算任务需要耗费很长时间,它的计算结果我们并不急着需要,在等待该线程计算完成的过程中,我们想充分利用着等待的时间,让CPU去响应其他的线程,等我们需要这个计算结果时,再去拿这个计算任务的返回值。
举个生活中的例子,假如我们想吃云吞做早餐,云吞要放到沸水中煮,在打开电磁炉煮热水,等待水沸腾的过程中,我们可以去把云吞从冰箱里拿出来解冻,此时电磁炉中的水是否沸腾我们不关心,等我们把所有云吞都解冻好了,真正需要放到沸水中煮时,再去打开电磁炉盖,关心此时的水是否已沸腾。
使用Callable接口的线程大概就是这么一个思想,在线程提交执行后,会立刻有返回类型(假设是V),虽然此时线程可能没执行完,之后我们可以通过调用Future.get()方法来获得这个返回值(如果你是把任务返回结果放到Future泛型接口中,你也可以把它放到实现Future接口的类FutureTask类中,同样调用FutureTask.get()方法来获得返回值,下面我会详细说)。
Future接口和FutureTesk类
那么,实现Callable接口的线程,执行结果返回给谁呢?谁来接收实现Callable接口的线程返回值?有两种方式,使用Future泛型接口或使用Future接口的类FutureTesk类。