简介
本文主要分析FutureTask的实现原理,以运行过程中state的变化为切入点,最终把整个过程归纳为三张流程图。
FutureTask的整体结构
private volatile int state; // 标记当前运行状态
private Callable<V> callable; // 实际运行的代码
private Object outcome; // callable运行结果
private volatile Thread runner; // 运行callable的线程
private volatile WaitNode waiters; // 等待callable运行完成的线程
FutureTask实现了获取运行结果的功能,那实现这个功能需要什么呢?
- 一个保存结果的变量,这是很容易想到的。这就是上面的outcome。
- 如果获取结果前,执行代码的线程还没跑完,那么获取结果的这个线程肯定得阻塞,所以就要一个阻塞队列。这就是上面的waiters。
FutureTask是可以打断任务的,那就得知道执行当前任务的线程,所以也需要保存runner。
callable很好理解,最后来看一下state。
private static final int NEW = 0; // 初始状态/正在运行
private static final int COMPLETING = 1; // 已经完成callable调用/执行callable中抛异常
private static final int NORMAL = 2; // 正常完成
private static final int EXCEPTIONAL = 3; // 运行出现异常
private static final int CANCELLED = 4; // 打断,但不会停止正在运行的任务
private static final int INTERRUPTING = 5; // 打断,会调用Thread#interrupt,停止正在运行的任务
private static final int INTERRUPTED = 6; // 打断完成
先看一下,后面再详细解释。
只可能有如下四种状态转移,刚开始可以先不要看COMPLETING和INTERRUPTING,这两个状态不是很好理解,它们主要起到并发时的保护作用。
NEW -> COMPLETING -> NORMAL
NEW -> COMPLETING -> EXCEPTIONAL
NEW -> CANCELLED
NEW -> INTERRUPTING -> INTERRUPTED
上述四种转移路径分别对应如下四种情况:
- 正常执行callable完毕,最后就是NORMAL。
- 执行callable中的代码时出了异常,最后就是EXCEPTIONAL。
- 调用了cancel,并且设置mayInterruptIfRunning为false,最后就是CANCELLED。
- 调用了cancel,并且设置mayInterruptIfRunning为true,最后就是INTERRUPTED。
现在对state的转移路径有了初步的认识,有点懵逼也没事,接下去分析run,get,cancel三个方法,就能把这些状态串起来了。也能明白INTERRUPTING和COMPLETING的作用了。
run
接下去看一下源码:
public void run() {
// 如果state不为NEW,或者cas赋值runner失败。那就不要去执行callable里的代码了。
// state不为NEW说明之前有其他线程调用过cancel,或者当前任务已经执行过了。cas失败说明有其他线程已经在执行当前任务了。
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
// 执行callable里的代码
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
// 异常处理
setException(ex);
}
if (ran)
// 正常结束处理
set(result);
}
} finally {
runner = null;
int s = state;
// 如果被cancel的可运行时打断需要的处理
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
接下去看一下setException和set
protected void setException(Throwable t) {
// cas修改state为COMPLETING
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
//结果赋值给outcome
outcome = t;
// 修改state为EXCEPTIONAL
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
// 唤醒waiters中等待的线程。
finishCompletion();
}
}
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
// 修改state为NORMAL
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
可以看到state状态COMPLETING和NORMAL(EXCEPTIONAL)是连续修改的,中间只有一个outcome,那为什么要多设置一个COMPLETING?具体的原因等看完get和cancel了再分析。
private void handlePossibleCancellationInterrupt(int s) {
// 等待state变为INTERRUPTED
if (s == INTERRUPTING)
while (state == INTERRUPTING)
Thread.yield(); // wait out pending interrupt
}
如果state是INTERRUPTING,handlePossibleCancellationInterrupt就自旋,等待state变为INTERRUPTED,具体原因后面分析。
get
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
代码很简单,就自己看看吧。
cancel
上图中的流程是在当前state为NEW的时候才执行的,因为当前state不为NEW说明callable已经执行完了,那打断就没有意义了。
上下两条分支主要区别就是有没有调用Thread#interrupt,也就是能不能实现运行中打断。
接下去看看源码
public boolean cancel(boolean mayInterruptIfRunning) {
// 设置INTERRUPTING或者CANCELLED
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try {
if (mayInterruptIfRunning) {
// 如果允许运行中打断
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
// 设置INTERRUPTED
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
// 不管允不允许运行中打断,都唤醒等待的线程。
finishCompletion();
}
return true;
}
代码很短。
接下去补充一下之前提到的几个问题。
补充
-
INTERRUPTING的作用
现在假设没有INTERRUPTING,那允许运行时打断的流程就是先调用Thread#interrupt,再把state设为INTERRUPTED,当然此时就要用cas进行赋值了。
考虑如下的场景:A线程(负责打断)Thread#interrupt调用完后,B线程(负责执行callable)因为被打断,会抛出异常(之前在sleep),或者继续执行(之前在park),那就会进入setException或者set,然后把state用cas改为COMPLETING,最后改为EXCEPTIONAL或者NORMAL,然后时间片再分给A线程,A线程再尝试去用cas把state改成INTERRUPTED就会失败了。所以本来state的最终状态应该是INTERRUPTED,最终输出CancellationException,但现在就是正常输出或者抛ExecutionException。 -
COMPLETING的作用
setException和set中能不能直接把state改为EXCEPTIONAL或者NORMAL?如下代码:protected void set(V v) { if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, NORMAL)) { outcome = v; finishCompletion(); } } protected void setException(Throwable t) { if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, EXCEPTIONAL)) { outcome = t; finishCompletion(); } }
这样很显然是不行的,因为get的时候会先判断state是否<=COMPLETING,如果>COMPLETING就会直接输出了,但如果按上面的写法,很可能outcome此时还是空的。
在上面的代码中,如果把outcome赋值写到if的上面可以吗?
这样有个小问题就是即使其它线程调用了cancel,还是会执行赋值操作。不过好像不影响输出。但整体代码就比较乱,作者本来用COMPLETING统一了没被打断时callable刚执行完的状态,就非常清晰。 -
handlePossibleCancellationInterrupt
这是在run最后执行的方法,作用就是如果当前state为INTERRUPTING,就自旋直到state变为INTERRUPTED。什么意思呢?INTERRUPTING代表cancel刚开始,还没有调用Thread#interrupt,INTERRUPTED代表Thread#interrupt已经调用过了。如果Thread(也就是runner)就执行一个FutureTask好像看不到作用,不自旋直接退出对结果也没什么影响,但是如果runner接下去还要执行其他的任务,那就很恐怖了,cancel中还有一个Thread#interrupt随时可能调用,那就会影响到其它的任务。
从这里可以看出INTERRUPTING的另一个作用:如果没有INTERRUPTING,那根本没法知道有没有其它线程在打断当前任务,有可能这个任务执行完了,runner都开始执行其他任务了,然后打断上一个任务的线程执行了Thread#interrupt,这就很麻烦了。
感叹一下Doug Lea是真牛逼啊。。。虽然整体流程比较简单,但是细节很多很多,水平有限,哪里没理解对,写的不好的地方请大佬指正。