在我们Java多线程系列的文章中,我们已经一起学习了Java内存模型、 线程 、 锁 、AQS原理、AQS的应用(CountDownLatch和Semaphore)以及阻塞队列等等。
今天我们来一起盘一盘应用场景很多的实现类,虽然我们不经常直接使用这些类,但是在各种地方都有它们的身影(比如 线程池 ),且它们很容易搞混,我称之为“ 可知结果的未来任务——FutureTask ” (叫法有点拗口哈,因为我实在想不出更贴切的名字了~ )
由于涉及到的接口定义和实现比较多(看标题就知道了。。), 我先让它们各自做个自我介绍,然后我们最后来看看所谓的“ 可知结果的未来任务 ”的源码实现 。
逐个介绍
Runnable
在之前介绍线程的时候,我们就知道了,创建线程的很多方法,其中实现Runnable接口就是其中的方式,其实就是定义了这个线程run方法中做了哪些事情:
源码中的注释还是比较好理解的,但是我们会发现,这个run方法没有任何返回值,也就是说我们根本就不知道,run方法的执行结果到底怎么样,这时Doug Lea大神就定义了另外一套接口 Callable 系列接口。
Callable
要想run方法有结果,最直接的方法就是将返回值类型void改成特定的类,好在Java有泛型编程,如下源码实现:
/** * A task that returns a result and may throw an exception. * Implementors define a single method with no arguments called * {@code call}. * * <p>The {@code Callable} interface is similar to {@link * java.lang.Runnable}, in that both are designed for classes whose * instances are potentially executed by another thread. A * {@code Runnable}, however, does not return a result and cannot * throw a checked exception. * * <p>The {@link Executors} class contains utility methods to * convert from other common forms to {@code Callable} classes. * * @see Executor * @since 1.5 * @author Doug Lea * @param <V> the result type of method {@code call} */ @FunctionalInterface 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; } 复制代码
我们和Runnable简单比较下:
- Callable有返回值
- Callable可以抛出异常。 除了我们想要的有返回值之外,大神还帮我们想到了 异常处理的问题,这样子线程任务里发生了异常,我们就可以做对应的处理了,而Runnable是不行的,丧失了一定的灵活和自定义的特性。如下demo演示下:
public class CallableDemo { public static void main(String[] args) throws Exception { Callable<String> callable = new Callable<String>() { @Override public String call() throws Exception { for (int i = 0; i < 5; i++) { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "处理业务中。。。" + i); } return "122333333"; } }; System.out.println(Thread.currentThread().getName() + "结果:" + callable.call()); } } //运行效果 /** main处理业务中。。。0 main处理业务中。。。1 main处理业务中。。。2 main处理业务中。。。3 main处理业务中。。。4 main结果:122333333 */ 复制代码
用法是不是很简单呢。 特点如下:
- 想要有结果,就必须等到业务处理完成,就像是线程卡住了自己,无法异步调用
- 从打印效果来看,不像Runnable重新开启了另外一个线程,是主线程自己调用,然后就卡住了
这时,大神又定义了另一个接口,它可以把call方法交给另一个线程异步执行,叫做“ 未来 ” 接口 —— Future ,如果有更牛逼的名字,请告知我哈~
Future
官方的解释如下: Future表示异步计算的结果。方法用于检查计算是否完成、等待计算完成以及检索计算结果。 只有在计算完成后,才能使用get方法检索结果,在必要时阻塞直到它准备好。取消是由cancel方法执行的。还提供了其他方法来确定任务是正常完成还是被取消。一旦计算完成,就不能取消计算。如果为了可取消性而使用Future,但不提供可用的结果,可以声明Future并返回null作为底层任务的结果。
简单来讲如下:
- 此接口用来代表一个异步计算的结果。可以用来获取一个异步结果,可以取消正在执行的任务,可以判断任务是否取消,可以判断任务的是否完成。
- 只有等任务完成,才会有结果返回。 总的来看, Future接口只是提供了获取异步结果的方法以及一系列操控异步执行任务的方法,但我感觉还没真正的是把任务异步交给了另一个线程。于是,大神在这基础之上又组合了一个类,叫未来任务接口—— RunnableFuture
RunnableFuture
/** * A {@link Future} that is {@link Runnable}. Successful execution of * the {@code run} method causes completion of the {@code Future} * and allows access to its results. 一个可运行的未来。run方法的成功执行将导致Future的完成,并允许访问其结果。 * @see FutureTask * @see Executor * @since 1.6 * @author Doug Lea * @param <V> The result type returned by this Future's {@code get} method */ public interface RunnableFuture<V> extends Runnable, Future<V> { /** * Sets this Future to the result of its computation * unless it has been cancelled. 除非已取消,否则将此Future设置为其计算的结果。 */ void run(); } 复制代码
从源码的继承关系来看, 它即继承了Runnable,又继承了Future,这样子的一个既可以创建异步线程来执行异步任务,又可以对异步任务进行操控的接口就这样诞生了。 至于里面的 run方法,一开始我也觉得很懵,为啥Runnable接口里面有了,这边又定义了一次? 结合 下图官方API ,你没看错,它其实就是Runnable的run方法,我猜它 这边又定义了一次,只是用来强调下,这是这个类的特有异步执行可操控未来的一个异步过程(就是强调这是属于该“未来任务”的执行方法,只不过是借用了Runnable,感觉有点像是应用了Adapter适配器设计模式~)
好了,上面的介绍其实都是铺垫,下面的才是本篇的主角,它实现了以上接口的所有特性,它才是真正的 未来任务实现类——FutureTask 。
在此,我们画一个UML类图,来对上面的各个接口定义做如下清晰展示:
正主:FutureTask
下面我们就来看看我们的正主: FutureTask 。
重要属性
/* * Revision notes: This differs from previous versions of this * class that relied on AbstractQueuedSynchronizer, mainly to * avoid surprising users about retaining interrupt status during * cancellation races. Sync control in the current design relies * on a "state" field updated via CAS to track completion, along * with a simple Treiber stack to hold waiting threads. * * Style note: As usual, we bypass overhead of using * AtomicXFieldUpdaters and instead directly use Unsafe intrinsics. 大概意思如下: 修订注意:这与依赖于AbstractQueuedSynchronizer的该类的以前版本不同,主要是为了避免在取消 竞赛期间保留中断状态让用户感到意外。当前设计中的同步控制依赖于通过CAS更新的“state”字段来 跟踪完成情况,以及一个简单的Treiber堆栈来保存等待的线程。 样式注意:与往常一样,我们绕过了使用AtomicXFieldUpdaters的开销,而是直接使用Unsafe intrinsic