小知识,大挑战!本文正在参与「程序员必备小知识」创作活动
FutureTask是一个未来任务,可以获取返回结果,通过get方法来完成,但get方法会阻塞直到任务执行完返回结果,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
01
继承结构
FutureTask实现了RunnableFuture,而RunnableFuture又继承了Future和Runnable,Future提供了返回结果的get方法,这里可能会有一个疑问,为什么submit方法返回的是Future而不是FutureTask。这是因为只想对外部调用者暴露get方法,而不想暴露FutureTask的run方法的能力。
02
属性解析
// 表示task状态
private volatile int state;
// 当前任务尚未执行
private static final int NEW = 0;
// 当前任务正在结束,一种临界状态
private static final int COMPLETING = 1;
// 当前任务执行结束
private static final int NORMAL = 2;
// 当前任务执行过程发送了异常,内部封装的callable.run()向上抛出了异常
private static final int EXCEPTIONAL = 3;
// 当前任务被取消
private static final int CANCELLED = 4;
// 当前任务中断中
private static final int INTERRUPTING = 5;
// 当前任务已中断
private static final int INTERRUPTED = 6;
// submit(runnable/callable)runnable 使用 装饰者模式伪装成Callable
private Callable<V> callable;
// 正常情况下,任务正常执行结束,outcome保存执行结果,callable返回值
// 非正常情况下:callable向上抛出异常,outcome保存异常
private Object outcome; // non-volatile, protected by state reads/writes
// 当前任务被线程执行期间,保存当前执行任务的线程对象引用
private volatile Thread runner;
// 因为会有很多线程去get当前线程结构
// 所以这里使用了一种数据结构 stack 头插 头取 的一个队列
private volatile WaitNode waiters;
FutureTask整个运行过程都需要通过当前任务运行状态来控制。
03
run方法
当通过submit提交任务时,最后执行的是execute方法,我们知道execute()方法最后调用的是task的run()方法,上面我们传进去的任务,最后被包装成了FutureTask,也就是说execute()方法最后会调用到FutureTask的run()方法。
// 线程任务执行入口
public void run() {
// cas失败,当前任务被其他线程抢占了
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
// 当前task一定是NEW状态,而且当前线程也抢占TASK成功
try {
// callable 就是程序员自己封装逻辑的callable 或者 装饰后的runnable
Callable<V> c = callable;
// 条件一:c!=null 防止空指针
// state==NEW 防止外部线程cancel掉当前任务
if (c != null && state == NEW) {
// 结果引用
V result;
// true callable代码块执行成功
// false 代码块执行失败,抛出异常
boolean ran;
try {
//调用程序员自己实现的callable或者装饰后的runnable
result = c.call();
//call方法没有抛出任何异常,ran会设置为true
ran = true;
} catch (Throwable ex) {
//进入catch说明程序员自己写的逻辑块有bug了
result = null;
ran = false;
//异常设置到outcome
setException(ex);
}
// call方法正常执行结束
if (ran)
// set就是设置结果到outcome
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
-
首先判断当前任务的运行状态,如果当前任务不是初始化状态或者修改为当前线程来运行任务失败,就直接返回
-
如果当前任务是初始化状态并且callable不为空就调用callable的call方法运行当前任务
-
将返回的结果值设置到outcome中,并唤醒waiterNode中所有等待的线程
-
更新当前任务状态
04
set方法
protected void set(V v) {
// 使用CAS方式设置当前任务状态为完成中
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
//将结果赋值给outcome之后,马上会将当前任务状态修改为NORMAL 正常结束状态
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
// 唤醒WaiterNode所有等待的线程
finishCompletion();
}
}
set方法主要做了三件事
-
实时更新当前任务执行状态
-
将结果值赋值给outcome
-
调用finishCompletion方法唤醒所有调用get方法而阻塞的线程
05
finishCompletion方法
private void finishCompletion() {
// 循环waitNode结点
for (WaitNode q; (q = waiters) != null;) {
// 使用cas设置 waiters为null,是因为怕外部线程使用 cancel取消当前任务
// 也会触发finishCompletion方法
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
//
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
//唤醒当前节点对应线程
LockSupport.unpark(t);
}
// next 当前节点下一个结点
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
}
finishCompletion方法也很简单,就是循环遍历waitNode的每个节点,将每个节点进行唤醒。
06
get方法
public V get() throws InterruptedException, ExecutionException {
//获取当前任务状态
int s = state;
// 表示 未执行,正在执行,正完成
if (s <= COMPLETING)
// 将线程加入WaitNode,进入队列等待
s = awaitDone(false, 0L);
// 返回执行结果
return report(s);
}
判断当前任务状态,如果还没完成就调用awaitDone方法使当前线程加入阻塞队列进行等待,最后通过report方法返回执行结果。
07
awaitDone方法
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
// 假设为0 不带超时
final long deadline = timed ? System.nanoTime() + nanos : 0L;
// 引用当前线程 封装成 WaitNode对象
WaitNode q = null;
boolean queued = false;
for (;;) {
// 当前线程唤醒是被其他线程使用中断这种方式唤醒的
// 返回true 后会将Thread的中断标记重置回false
if (Thread.interrupted()) {
// 将当前线程node出队
removeWaiter(q);
// get方法抛出中断异常
throw new InterruptedException();
}
// 假设当前线程是被其他线程使用unpark唤醒的话,会正常自旋,
// 走下面逻辑
// 获取当前任务最新状态
int s = state;
4. // 说明当前任务已经有结果了
if (s > COMPLETING) {
//不等于空说明已经为当前线程创建过node了,此时需要将
// node。thread=null helpgc
if (q != null)
q.thread = null;
//直接返回当前状态
return s;
}
// 表示当前任务接近完成
else if (s == COMPLETING) // cannot time out yet
// 让当前线程再释放CPU,进行下一次抢占CPU
Thread.yield();
// 条件成立第一次自旋,当前线程还未创建WaitNode 对象,
1. // 此时为当前线程创建WaitNode对象
else if (q == null)
q = new WaitNode();
// 第二次自旋,当前线程已经创建WaitNode对象了,
2. // 但是node对象还未入队
else if (!queued)
//当前线程node节点next指向原队列的头节点,waiters一直指向队列的头
// cas方式设置waiters引用指向线程node
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
// 第三次自旋
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
//当前get操作的线程就会被park,线程状态变为waiting状态,想当与休眠
3. // 除非有其他线程将你唤醒,或者将当前线程中断
else
LockSupport.park(this);
}
}
这里我们假设调用get()时任务还未执行,也就是其状态为NEW,我们试着按上面标示的1、2、3、4走一遍逻辑:
-
第一次循环,状态为NEW,直接到1处,初始化队列并把调用者线程封装在WaitNode中;
-
第二次循环,状态为NEW,队列不为空,到2处,让包含调用者线程的WaitNode入队;
-
第三次循环,状态为NEW,队列不为空,且已入队,到3处,阻塞调用者线程;
-
假设过了一会任务执行完毕了,根据run()方法的分析最后会unpark调用者线程,也就是3处会被唤醒;
-
第四次循环,状态肯定大于COMPLETING了,退出循环并返回;
总结
-
未来任务是通过把普通任务包装成FutureTask来实现的。
-
通过FutureTask不仅能够获取任务执行的结果,还有感知到任务执行的异常,甚至还可以取消任务;
-
AbstractExecutorService中定义了很多模板方法,这是一种很重要的设计模式;
-
FutureTask其实就是典型的异常调用的实现方式。