前言
FutureTask的使用方法已经在上一篇进行了讲解,其实它和SynchronousQueue很像,执行task的线程是生产者,获取执行结果的线程是消费者,消费者阻塞的原因不是生产者还没来,是因为生产者还没有生产出来执行结果。只不过,这里只有一个生产者(FutureTask
对象),却可以对应到多个消费者(对同一个FutureTask
对象调用get
的不同线程)。
为了方便称呼,本文以生产者和消费者来指代两种线程。
状态
FutureTask的重点在于对task(Callable.call()
)的执行的管理,而FutureTask通过一个volatile的int值来管理task的执行状态。
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
这个state就和AQS的state一样,是最重要的属性,因为state就时刻反映了task的执行状态。
* Possible state transitions:
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
可能状态转移过程是上面这4种(朋友,听说过状态机吗)
初始化时FutureTask
的state是NEW,这是构造器保证的。
这种状态的转换都是不可逆的,某些过程中还可能存在中间状态,但这种中间状态存在的时间很短,且马上也会变成相应的最终状态。所以可以认为,只要状态不是NEW的话,就可以认为生产者执行task已经完毕。关于这一点,isDone
函数可以替我作证:
public boolean isDone() {
return state != NEW;
}
调用这些函数可以使得状态发生转移(具体过程后面讲解):
set(V v)
使得NEW -> COMPLETING -> NORMAL。setException(Throwable t)
使得NEW -> COMPLETING -> EXCEPTIONAL。cancel(boolean mayInterruptIfRunning)
可能有两种状态转移:- 当
mayInterruptIfRunning
为false时,使得NEW -> CANCELLED。 - 当
mayInterruptIfRunning
为true时,使得NEW -> INTERRUPTING -> INTERRUPTED。
- 当
注意,图中的“取消”二字打了引号,因为消费者实际上不可能使得正在执行的生产者线程咔嚓终止掉。
消费者链表
前面提到,对同一个FutureTask
对象调用get
的不同线程的都属于消费者,当生产者还没有执行完毕task时,调用get
会阻塞。而做法是将消费者线程包装成一个链表节点,放到一个链表中,等到task执行完毕,再唤醒链表中的每个节点的线程。这种做法类似于AQS的条件队列和signalAll
。
反正最终链表上的所有节点都将被唤醒,所以链表是栈的逻辑结构,这样只用保存栈顶head指针,稍微简单一点。
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
从上面链接节点的成员来看,确实很简单,只需要保存线程对象和next指针即可。
成员
/** task执行状态 */
private volatile int state;
/** task */
private Callable<V> callable;
/** 执行结果,可能是泛型类型V 或 抛出的异常。这都是前两种状态转移才会设置的 */
private Object outcome; // non-volatile的,因为最终会对state进行CAS操作,从而保证可见性
/** 执行task的生产者 */
private volatile Thread runner;
/** 消费者栈的head指针 */
private volatile WaitNode waiters;
outcome
是Object类型,可以存任何类型对象。这样既可以存泛型类型V,也可以存异常对象。- 当调用
new Thread(FutureTask对象).start()
时,生产者线程便创建并开始运行了,并且会在FutureTask#run()
的刚开始就把生产者线程存放到runner
中。 - 当调用
FutureTask对象.get()
时,如果task还未执行完毕,当前消费者线程会被包装成一个节点扔到栈中去。
构造器
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
两个构造器都保证了初始时状态为NEW。除了可以接受Callable
之外,还可以接受Runnable
,但也是马上通过适配器模式把Runnable
包装成一个Callable
而已。
实现Runnable接口
先来看一看FutureTask对Runnable接口的实现。
public void run() {
// 执行run函数的前提是state为NEW,且生产者位置还没有被占用
if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
return;
//执行到这里,说明此时state为NEW,且生产者位置已经被当前线程占领
try {
Callable<V> c = callable;
if (c != null && state == NEW) { //直到这里,还会检查一次state是否为NEW
V result;
boolean ran;
try {
result = c.call(); // 执行task
ran = true;
} catch (Throwable ex) { // 生产者自身执行task过程中,抛出了异常
result = null;
ran = false;
setException(ex);
}
if (ran) // 生产者顺利执行完了task
set(result);
}
} finally {
// 执行完毕释放生产者线程对象引用
// FutureTask对象.run() 不可能被调用第二次,因为此时state肯定不是NEW了,run方法的第一句肯定通不过
runner = null;
// 消费者线程可能使得task取消,其中一种状态转移是NEW -> INTERRUPTING -> INTERRUPTED
// 这种转移有中间状态,需要检测这种中间状态变成最终状态
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
在函数的一开始,需要检测state是否为NEW,且当前线程对象需要占领runner
,并且在退出run函数之前,一直都会占领着runner
。
if (c != null && state == NEW)
会再检测一次state是否为NEW,如果为NEW,就开始执行task。生产者自己执行task时(c.call()
),有两种情况:
- 顺利执行完task,然后调用
set(result)
。 - 执行task途中抛出异常,然后调用
setException(ex)
。
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
如果顺利执行完task,outcome
会被赋值为执行结果。另外,状态转移是NEW -> COMPLETING -> NORMAL的过程,并且这个中间状态是只存在很短的时间的。
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
如果执行task途中抛出异常,outcome
会被赋值为抛出的异常对象。另外,状态转移是NEW -> COMPLETING -> EXCEPTIONAL的过程,并且这个中间状态是只存在很短的时间的。
上面两个函数都调用了finishCompletion
:
private void finishCompletion() {
// 保证调用此函数时,state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
LockSupport.unpark(t);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
}
此函数负责唤醒所有消费者线程,原理很简单,内层循环遍历链表的每个节点,唤醒每个节点的线程对象。而外层循环在刚开始时,负责给局部变量q
赋值,在退出外层循环时,负责检查waiters
是否已经被赋值为null(当然检查结果肯定成立)。
最后还会调用done
函数,但这只是空实现,这是用来给使用者拓展用的,可以让生产者线程在执行完毕前多做一点善后工作。
} finally {
// 执行完毕释放生产者线程对象引用
// FutureTask对象.run() 不可能被调用第二次,因为此时state肯定不是NEW了,run方法的第一句肯定通不过
runner = null;
// 消费者线程可能使得task取消,其中一种状态转移是NEW -> INTERRUPTING -> INTERRUPTED
// 这种转移有中间状态,需要检测这种中间状态变成最终状态
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
最后还有finally块,先执行runner = null
,执行后不用担心FutureTask对象.run()
被调用两次,因为此时state肯定不是NEW了,run方法的第一句肯定通不过。
再判断当前是否为>= INTERRUPTING
,这种情况可能是遇到了state的中间状态,所以需要调用handlePossibleCancellationInterrupt
自旋等待直到最终状态。
private void handlePossibleCancellationInterrupt(int s) {
if (s == INTERRUPTING)
while (state == INTERRUPTING)
Thread.yield();
}
但是我们从前面的讲解从来没见到过有把state设置为INTERRUPTING
的操作,其实这个操作是消费者线程干的,接下来将讲解。
实现Future接口
再来看一看FutureTask对Future接口的实现。
普通get、超时get
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
此函数判断当前state,并根据情况调用awaitDone
进行阻塞等待。
- 如果state为NEW,那么生产者还没开始执行呢,肯定得阻塞等待。
- 如果state为COMPLETING,那么生产者线程马上执行完了,并且是 生产者正常执行完的过程(即前两种状态转移),那么也阻塞等待,即使马上会被唤醒。
public V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
if (unit == null)
throw new NullPointerException();
int s = state;
if (s <= COMPLETING &&
(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
throw new TimeoutException();
return report(s);
}
而这个函数是get的超时版本,所以调用awaitDone
的实参设置不一样。并且在退出awaitDone
函数时,要检查返回值。返回值总是state的最新值:
- 如果state为NEW,那么说明返回是因为超时,因为生产者执行task期间state一直都为NEW(直到执行了
set
或setException
才会改变),所以说明返回时,要么生产者还没执行完task,要么生产者根本还没开始执行。 - 如果state为COMPLETING,那么说明生产者即将执行完毕,但还没有设置返回值。虽然运气不好,但也只好算作超时。
来看看awaitDone是怎么做到阻塞等待和超时版本的阻塞等待的:
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false; //代表入栈成功
for (;;) {
if (Thread.interrupted()) { //消费者线程被中断
removeWaiter(q);
throw new InterruptedException();
}
int s = state; //获取最新的state
//分为两种情况:
//1. NORMAL或EXCEPTIONAL,生产者正常执行完task
//2. 其余情况,其他消费者取消了task
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // 正常执行完的中间状态,自旋等待
Thread.yield();
else if (q == null) // state为NEW,说明生产者线程还没执行完
q = new WaitNode();
else if (!queued) // 节点已经创建,但还没入栈
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,//CAS保证了入栈的正确性
q.next = waiters, q);//先把新节点q的next指向旧栈顶,然后更新栈顶为q
// 此时节点已经入队,但是state还是NEW,只能阻塞等待了
else if (timed) { //如果是超时版本
nanos = deadline - System.nanoTime(); //获得剩余时间
if (nanos <= 0L) { //剩余时间小于等于0,说明已经超时,但task还没执行完。
removeWaiter(q);
return state; //注意这里返回的不是局部变量,而是最新值。运气好的话,可能返回的不是NEW
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
}
- 刚开始检查消费者线程的中断状态,如果被中断,说明消费者线程不应该再等待了。那么从栈中移除节点,并抛出中断异常。比如消费者线程在
LockSupport.park(this)
后被中断而唤醒。 - 接下来检查当前state是什么(可能是 第一次循环执行到这里,也可能是 阻塞后被唤醒下一次循环执行到这里),分为两种情况:
- 如果是NORMAL或EXCEPTIONAL,说明生产者正常执行完task,没有受到消费者的取消动作干扰。这两种都是最终状态,直接返回即可。
- 如果是CANCELLED、INTERRUPTING、INTERRUPTED,那么说明别的消费者“取消”了task。其中有一种中间状态,这无所谓,因为这三种状态都代表了取消。
- 另外,这里是
return s
返回局部变量,而不是return state
返回最新成员。因为大部分状态都是最终状态,即使有一个中间状态,它的最终状态也是已知的了。
- 如果生产者正在设置执行结果,那么自旋等待。
- 接下来就是正常阻塞前的流程:
- 发现state还是NEW,所以新建节点。
- 新建节点后,发现还没有入队,那么入队。
- 入队完毕后,当前线程就可能马上阻塞了。
- 阻塞根据参数有两种版本:
- 无限阻塞。直接调用
LockSupport.park(this)
。 - 超时阻塞。先计算得到剩余时间还有多少(正常阻塞前的流程也会花点时间的),然后调用
LockSupport.parkNanos(this, nanos)
。注意,如果超时阻塞后因为超时而唤醒时,也会走到这里,然后发现已经超时,那么栈中移除节点,并返回最新state。- 注意,返回的是
return state
,这里其实除了返回NEW以外,其他state也是都是可能返回的,在调用removeWaiter(q)
期间可能会发现一些事情。比如,在此期间,生产者线程正在执行set
,那么state可能是COMPLETING或NORMAL;比如,在此期间,别的消费者线程正在执行cancel
,那么state可能是CANCELLED、INTERRUPTING、INTERRUPTED。 - 关于上面这一点,其实就是给已经超时的超时操作多个机会,说不定执行完
removeWaiter(q)
,state就变成NORMAL了呢。
- 注意,返回的是
- 无限阻塞。直接调用
来看一下removeWaiter(q)
是怎么移除节点的,注意实参是可能为null的,当q局部变量还没创建,当前线程就被中断时。
private void removeWaiter(WaitNode node) {
// 如果实参为null,那么啥也不做
if (node != null) {
node.thread = null; // 把node的thread设置为null作为标记。注意,此后node再无作用
retry:
for (;;) { // 致力于寻找pred -> q(thread == null) -> s的结构
for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
s = q.next;
if (q.thread != null) //发现q是未取消节点,更新pred和q
pred = q;
// 发现q是取消节点,但有两种情况
else if (pred != null) { // 如果q不是首节点
pred.next = s; //将pred -> q -> s变成 pred -> s
if (pred.thread == null) // 如果发现pred也是一个取消节点,这说明q还在链表中
continue retry;
}
else if (!UNSAFE.compareAndSwapObject(this, waitersOffset, //如果q是首节点
q, s)) //那就使得栈顶下移一个节点即可
continue retry;
}
break;
}
}
}
此函数参数node的thread被标记为null后,node就没有作用了。因为遍历过程中,我们是去寻找所有的被标记的节点,然后尝试移除它们。
- 如果发现一个取消节点是首节点,那么使得head下移一个节点即可。
- 如果发现一个取消节点不是首节点,那么将
pred -> q -> s
变成pred -> s
(执行pred.next = s
)。如下图所示,从链表任意节点出发,都不能到达这个q
节点。
- 不过有时候执行
pred.next = s
其实是一个无效操作,实际上并没有把q移除,此时需要重新开始循环continue retry
(关于这一点,LinkedTransferQueue#unsplice也有一样的判断)。这种情况是pred是一个取消节点时,如下图所示,q
节点还是处于链表中:
最后,我们再回到两个get函数的report(s)
,分析report()
函数之前,先看看这个实参s
可能是什么值。 - 首先这个值是
awaitDone
调用后返回的。 - 如果是无限阻塞地调用
awaitDone
,那么只可能返回s > COMPLETING
的值。 - 如果是超时阻塞地调用
awaitDone
,虽然可能返回NEW或COMPLETING,但是在get(long timeout, TimeUnit unit)
中会马上抛出超时异常的。 - 总之,实参
s
只能是s > COMPLETING
的值。
private V report(int s) throws ExecutionException {
Object x = outcome;//不管当前outcome是不是null,直接赋值
if (s == NORMAL)//如果状态是NORMAL,则outcome肯定不是null
return (V)x;
if (s >= CANCELLED)//如果状态是取消的状态(这里其实不可能是INTERRUPTING)
throw new CancellationException();
throw new ExecutionException((Throwable)x);//如果状态是EXCEPTIONAL,那么抛出这个异常对象
}
cancel、isCancelled
之前说过,消费者实际上不可能使得正在执行的生产者线程咔嚓终止掉,接下来将解释。我们先来回顾一下,生产者线程在run函数中,直到执行set
或setException
之前,都在正常执行task中,而既然没有执行这两个函数,说明这段时间state还是为NEW的。
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
而cancel
函数执行前提就是state是NEW,在生产者线程执行set
或setException
之前,都是可以CAS成功的。
如果消费者是在生产者线程执行run方法的if (c != null && state == NEW)
之前就执行了cancel
函数,那么才可以终止生产者执行task。
如果消费者是在生产者线程执行run方法的if (c != null && state == NEW)
之后才执行的cancel
函数,那么将不能终止生产者。
- 如果参数是false,state从NEW修改为CANCELLED。但修改state,并不能使得生产者线程运行终止。
- 如果参数是true,state从NEW修改为INTERRUPTING,中断生产者线程后,再修改为INTERRUPTED。我们知道,中断一个正在运行的线程,线程运行状态不会发生变化的,只是会设置一下线程的中断状态。也就是说,这也不能使得生产者线程运行终止。除非生产者线程运行的代码(
Callable.call()
)时刻在检测自己的中断状态。
那你可能会问,这种情况既然不能真的终止生产者线程,那么这个cancel
函数有什么用,其实还是有用的:
- 如果参数为true,那么会去中断生产者线程。但生产者线程能否检测到,取决于生产者线程运行的代码(
Callable.call()
)。 - 状态肯定会变成CANCELLED或INTERRUPTED,新来的消费者线程会直接发现,然后在
get
函数中不去调用awaitDone
。 - 对于生产者线程来说,执行task期间不会影响。但最后执行
set
或setException
,会发现这个state,然后不去设置outcome
。
最后执行了finishCompletion
函数,唤醒所有的消费者线程。
另外,注意,多个消费者来调用cancel
函数,最多只有一个能够成功,即返回true。
public boolean isCancelled() {
return state >= CANCELLED;
}
这三种状态都属于被取消了。
isDone
public boolean isDone() {
return state != NEW;
}
只要状态不是NEW的话,就可以认为生产者执行task已经完毕,或者已经被取消(取消的三种情况,上面也讲了)。
普通写和CAS写混合
在实现中可以看到有时候使用普通写的语义,有时候使用CAS写。
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
比如cancel
函数中的UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED)
这里使用的普通写,其他线程可能不能马上看到,但这没关系。因为:
- 一来,这个状态转移是唯一的。INTERRUPTING只能变成INTERRUPTED。其他线程暂时看不到 INTERRUPTED 也没关系。(注意,暂时看不到 INTERRUPTING,会导致
handlePossibleCancellationInterrupt
自旋) - 二来,
finishCompletion
中也有对其他volatile字段的CAS写操作。这样做会把之前的普通写都刷新到内存中去。
总结
FutureTask
整合了Callable
对象,使得我们能够异步地获取task执行结果。- 执行
FutureTask.run()
的线程就相当于生产者,生产出执行结果给outcome
。执行FutureTask.get()
的线程就相当于消费者,它们会阻塞等待直到执行结果产生。 - 如果生产者线程已经开始执行
Callable.call()
,那么消费者调用cancel
,实际上是无法终止生产者的运行的。