FutureTask 源码解析
在这篇文章中,我将采用 “自顶向下” 的方式来讨论 FutureTask 的源码。在深入分析每个 FutureTask 的方法的源码之前,我都会先概述这个方法的整体执行逻辑,以便提供一个更清晰的理解框架。在大家对方法的执行逻辑有了整体了解之后,我再以注释的形式对源码进行讨论,并尽可能指出某行源码对应的是方法整体执行逻辑中的哪一步。
通常情况下,我们不会直接创建 FutureTask 实例。当我们通过 ExecutorService 接口的 submit()
方法向线程池提交任务时,线程池会将任务封装成一个 FutureTask 实例,从而间接创建一个 FutureTask 实例。需要注意的是,如果使用 execute()
方法提交任务,任务是不会被封装成 FutureTask 实例的。
在深入讨论 FutureTask 源码之前,为了帮助大家更好地理解,我们需要先了解几个基本前提。
- FutureTask 的
get()
方法可能会被多个线程同时调用,也就是说get()
方法存在并发。 - FutureTask 的
run()
方法也同样有可能会被多个线程同时调用,但最终只会有一个线程在run()
方法的内部成功抢夺到执行权,其它抢夺执行权失败的线程会直接从run()
方法中返回。 - FutureTask 的
cancel()
方法也存在并发,可能会有多个线程同时调用cancel()
方法去尝试撤销任务,与run()
方法一样,最终只会有一个线程成功获取到cancel()
方法的执行权。

图 1 get()
、run()
、以及 cancel()
方法均存在并发
了解完这些基本前提,才能更好地理解为什么在 FutureTask 的源码中,有些地方要使用 CAS(compare and swap)方式赋值。
FutureTask 的字段
我们先来看看和 FutureTask 的状态有关的字段。
1 |
|
代码清单 1 FutureTask 的字段
我们从上述代码可以看到,FutureTask 一共有六种状态,分别是 COMPLETING、NORMAL、EXCEPTIONAL、CANCELLED、INTERRUPTING、INTERRUPTED。FutureTask 所有可能的状态转移路径如下图所示。

图 2 FutureTask 状态转移路径
在后续小节中,我们将结合源码来深入讨论 FutureTask 的这六种状态。读者可以结合这张 FutureTask 的状态转移路径图来更好地阅读和理解本文的内容。
在 FutureTask 的六种状态中,有两种状态(INTERRUPTING 和 INTERRUPTED)与中断有关。关于中断,我们需要知道的是:在任务执行期间,如果有其它线程调用了执行者线程(执行任务的线程,请参阅代码清单 1 中的 runner 字段)的 interrupt()
方法,执行者线程并不一定会抛出中断异常。这完全取决于执行者线程正在执行的任务代码本身是否包含响应中断的代码逻辑。如果任务代码中没有相应中断的代码逻辑,执行者线程将继续执行,而不会受到任何影响。
如果需要响应中断,任务代码该如何编写呢?其实很简单,只需在任务代码中使用 try-catch 块捕获 InterruptedException 异常即可。
FutureTask 的构造函数
我们接着看看 FutureTask 的构造函数。FutureTask 有两个构造函数,分别是:
- FutureTask(Callable
callable) - FutureTask(Runnable rnunable, V result)
形参类型为 Callable 接口的这个构造函数其实很简单,它只是简单地把用户提交的任务保存到 callable 字段中,如代码清单 2 所示。
1 |
|
代码清单 2 FutureTask(Callable
Callable 类型的任务有返回值,而 FutureTask 的 get()
方法的返回值就是 Callable 任务的返回值,这点我们很好理解。但 Runnable 任务没有返回值,那么 FutureTask 是如何获取并返回 Runnable 任务的执行结果的呢?要解答这个问题,让我们直接看源码,如代码清单 3 所示。
1 |
|
代码清单 3 FutureTask(Runnable, V) 构造函数
run() 方法
run()
方法是执行者线程执行任务代码的入口。run()
方法是存在并发的。线程之间会通过 CAS 方式抢夺任务的执行权,哪个线程成功地通过 CAS 方式把自己设置到 FutureTask 对象的 runner 字段,就表示哪个线程就成功抢夺到了任务的执行权。
run()
方法的整体执行流程如下所述:
- 如果任务的状态为 NEW,并且通过 CAS 的方式成功抢夺到了任务的执行权,则开始执行任务。而在执行任务代码之前,还会对任务的状态进行一次检测:如果状态依然为 NEW,则正式开始执行任务代码,否则放弃执行;
- 如果任务成功执行完成,则通过
set()
方法把任务执行结果赋值给 outcome 字段;如果任务执行期间抛出了异常,则通过setException()
方法把抛出的异常赋值给 outcome 字段。
如果在任务执行期间,任务的状态发生了变化,比如有线程调用 FutureTask 的cancel()
方法取消了任务,那么任务的状态就会变为 CANCELLED 或 INTERRUPTING 或 INTERRUPTED。但无论是哪一种状态,只要不是 NEW,那么任务执行完成后,无论执行成功还是执行失败,都不会把任务执行结果或异常对象赋值给 outcome 字段,而是什么也不做,直接从run()
方法返回。 - 当然,无论是哪种情况,即无论是执行成功,还是执行失败,抑或是任务执行期间任务的状态发生了变化,在从
run()
方法返回之前,都会检查一下当前任务是不是正处于 INTERRUPTING 状态(正在中断),如果是,则执行者线程会调用 Thread::yield 方法放弃处理器的使用权,等待其它线程完成对任务的中断工作,直到任务的状态变为 INTERRUPTED 就说明其它线程已经完成对任务的中断工作,这时执行者线程才会从run()
方法返回。
结合上面描述的 run()
方法的总体执行流程,让我们看一下 run()
方法的源码,如代码清单 4 所示。
1 |
|
代码清单 4 run() 方法
set() 方法
如果任务执行成功且任务状态没有发生变化(即依然为 NEW),则会通过 set()
方法将任务的执行结果保存到 outcome 字段,如代码清单 5 所示。
1 |
|
代码清单 5 set() 方法
setException() 方法
若在任务执行期间抛出了异常,则会执行 setException()
方法把抛出的异常赋值给 outcome 字段,如代码清单 6 所示。
1 |
|
代码清单 6 setException()
finishCompletion() 方法
1 |
|
代码清单 7 finishCompletion() 方法
handlePossibleCancellationInterrupt() 方法
最后我们再看一下 handlePossibleCancellationInterrupt()
方法,这个方法对应的就是上文描述的 run() 方法的整体执行流程的第 3 步,即无论任务执行成功与否,都会调用这个方法来检查一下任务状态是否为 INTERRUPTING,并据此决定要不要让权等待,如代码清单 8 所示。
1 |
|
代码清单 8 handlePossibleCancellationInterrupt() 方法
get() 方法
get()
方法的总体执行逻辑相对比较简单,可概括如下:
为了方便叙述,任务状态用 state 表示。
当
state <= COMPLETING
时,表示如下三种情况之一:- 任务还没有开始执行;
- 任务已经开始执行了,但还没有执行完;
- 任务已经执行完了,但还没有将结果保存到 outcome 字段。
无论是哪种情况,线程都会阻塞等待(阻塞等待的执行逻辑对应于
awaitDone()
方法)。如果
state > COMPLETING
,表示任务执行结果已经保存到 outcome 字段。但此时还不能确定任务到底是执行成功(NORMAL),还是执行失败(EXCEPTIONAL),异或是被取消(CANCELLED、INTERRUPTED)了。
如果执行成功,则直接返回任务执行结果,否则抛出异常(这一步的执行逻辑对应于report()
方法)。
1 |
|
代码清单 9 get() 方法
可以看到,其实 get()
方法把核心的代码逻辑都放在了 awaitDone()
和 report()
这两个方法中。所以接下来让我们直接看看这两个方法的源码。
awaitDone() 方法
awaitDone()
方法的整体执行逻辑可概括如下。
state 表示任务状态。
当前线程正是因为
state <= COMPLETING
才进入到awaitDone()
方法,所以当前线程会先检查 state 是否为 COMPLETING。如果是,则调用 Thread::yield 方法进行让权等待;为什么不调用 LockSupport::park 进行阻塞等待,而是调用 Thread::yield 进行让权等待呢?
从上文对
set()
和setException()
方法的讨论可知,执行者线程在将执行结果保存到 outcome 字段之后,就会马上将 state 修改为 NORMAL 或者 EXCEPTIONAL。也就是说 COMPLETING 状态的持续时间不会太长,而线程从调用 LockSupport::park 开始阻塞直至被 LockSupport::unpark 唤醒,期间的执行开销还是挺大的,所以没必要调用 LockSupport::park 进行阻塞等待。只需要调用 Thread::yield 放弃处理器使用权,等到下一次再被调度上处理器运行,state 大概率已经大于 COMPLETING。如果 state 不是 COMPLETING,而是 NEW,则当前线程会先把自己包装成一个 WaitNode 结点,然后以 CAS 方式自旋入队(等待队列);
一旦入队成功,则调用 LockSupport::park 进行阻塞等待。
调用 LockSupport::park 阻塞等待任务执行结果的线程有两种被唤醒的方式:
- 一种是被其它线程中断唤醒(LockSupport::park 是响应中断的);
- 另一种唤醒方式是被其它线程使用 LockSupport::unpark 唤醒。
但不管是被哪种方式唤醒,阻塞线程被唤醒后都会再次检查任务状态。
- 若
state > COMPLETING
,则先出队(把 WaitNode 结点的 thread 字段置为 null),然后从awaitDone()
方法返回并进入report()
方法的代码逻辑中处理任务执行结果; - 若
state == COMPLETING
,则调用 Thread::yield 进行让权等待; - 若
state == NEW
,并且线程是被中断方式唤醒的,则出队(会调用removeWaiter()
方法清理一下等待队列中 thread 字段为 null 的结点),然后直接抛出 InterruptedException 中断异常。
查看 FutureTask 的源码可知,如果线程是被其它线程通过调用 LockSupport::unpark 的方式唤醒的话,那任务状态必不可能是 NEW。因为只有 finishCompletion()
方法会调用 LockSupport::unpark 唤醒等待队列中的线程,而 finishCompletion()
方法又只会被 cancel()
、set()
、setException()
这三个方法调用,而这三个方法调用 finishCompletion()
方法之前都会先以 CAS 的方式把任务状态修改为大于 COMPLETING 的某一个状态,所以不可能出现线程被 LockSupport::unpark 方式唤醒了,任务状态却还是 NEW。
由于 awaitDone()
方法有 7 个 if-else 语句块,如果按序阅读这些 if-else 语句块可能很难领会到 awaitDone()
源码的核心思想,所以大家可以先按我标注的顺序阅读这些 if-else 语句块。
在对 awaitDone()
源码的执行逻辑有了基本的了解后,大家可以自行再按序阅读这些 if-else 语句块。同样地,我将以注释的方式讨论 awaitDone()
方法的源码,如代码清单 10 所示。
1 |
|
代码清单 10 get() 方法
至此,awaitDone()
方法的代码逻辑已讨论完毕。接下来让我们看看 report()
方法。
report() 方法
能执行到 report()
方法,说明任务执行结果肯定已经保存到 outcome 字段中。但此时还不能确定任务到底是执行成功(NORMAL),还是执行失败(EXCEPTIONAL),异或是被取消(CANCELLED、INTERRUPTED)了。
而 report()
方法就是要根据任务的状态进行相应的处理,如果执行成功,则返回保存在 outcome 字段中的结果,否则抛出异常。现在让我们直接来看 report()
方法的源码吧。
1 |
|
代码清单 11 report() 方法
可以看到,report()
方法的源码其实很简单。
removeWaiter() 方法
removeWaiter()
方法只会被 “被中断唤醒的线程” 执行。该方法用于清理等待队列中 thread 字段为 null 的结点。
1 |
|
代码清单 12 removeWaiter() 方法