1 概述
FutureTask类是Future接口和Runnable接口的实现类,主要为了解决异步计算并获取返回值,缺点也很明显,获取返回值的方法get是阻塞的,如果获取不到返回值会阻塞当前线程,是典型的阻塞编程。
下面是一个使用用例:
public class CallableApp implements Callable<String> {
private String name;
public CallableApp(String name) {
this.name = name;
}
@Override
public String call() throws Exception{
Thread.sleep(5000);
return name;
}
public static void main(String[] args) throws Exception {
//构造方法1
FutureTask task = new FutureTask(new CallableApp("Hello World!"));
//构造方法2
// FutureTask task = new FutureTask(new Runnable() {
// @Override
// public void run() {
// try {
// Thread.sleep(5000);
// System.out.println(Thread.currentThread().getName() + "执行完了!");
// }catch (InterruptedException e){
// e.printStackTrace();
// }
//
//
// }
// }, "Hello World!");
//启动线程
new Thread(task).start();
//阻塞当前线程直到获取返回值
System.out.println(task.get());
}
}
那么如何获得非阻塞编程?答案是通过谷歌Guava库的ListenableFuture或者JDK8自带的CompletableFuture类。有问题才会有创新,本文主要围绕着使用讲解FutureTask的源码,其余的见后续博文。
2 构造函数
FutureTask有两种构造方法,第一种的入参是一个Callable对象,线程启动时候执行Callable种的call方法
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
第二种有两个入参,分别是Runnable接口的实现类和返回结果,但是本质和第一种是一样的,会通过适配器模式进行封装为Callable接口的实现类。
public FutureTask(Runnable runnable, V result) {
//将封装Runnable接口的实现类成Callable接口的实现类
this.callable = Executors.callable(runnable, result);
//将状态设置为初始化
this.state = NEW; // ensure visibility of callable
}
public static <T> Callable<T> callable(Runnable task, T result) {
//非空校验
if (task == null)
throw new NullPointerException();
//返回封装后对象
return new RunnableAdapter<T>(task, result);
}
//适配器模式可以理解为两个插头不适应,中间加了个转换头
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
//执行任务
task.run();
//返回参入的结果
return result;
}
}
3 全局变量
运行状态state使用volatile修饰,保证所有调用get方法获取结果的线程的可见性。
其主要有以下4种转换状态:
1)NEW -> COMPLETING -> NORMAL(正常执行完)
2)NEW -> COMPLETING -> EXCEPTIONAL(发生异常)
3)NEW -> CANCELLED(被取消)
4)NEW -> INTERRUPTING -> INTERRUPTED(被中断)
//FutureTask对象的运行状态
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;
//构造方法中被赋值callable接口的实现类,是具体干活的
private Callable<V> callable;
//存储返回结果或异常信息
private Object outcome; // non-volatile, protected by state reads/writes
//调用Callable中call方法的线程
private volatile Thread runner;
//使用一种叫Treiber stack的栈,栈里面的每个节点存储阻塞线程,特点是先进后出,可以用于并发场景
private volatile WaitNode waiters;
WaitNode类是栈中存储信息的等待节点,用于记录排队的线程,通过next引用指向下一个节点
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
4 正常运行
线程启动调用run方法
public void run() {
//判断状态是否为初始化状态
//通过CAS操作将执行全局变量runnerrun指向调用Callable的线程,即执行到run方法的线程
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
//再次校验状态是否为NEW且传入的callable不为空
if (c != null && state == NEW) {
V result;
//标记变量,ture表示正常执行完,false表示发生异常
boolean ran;
try {
//调用Callable中的call方法,获取返回值
result = c.call();
ran = true;
} catch (Throwable ex) { //捕获异常
//返回结果设置为空
result = null;
ran = false;
setException(ex);
}
if (ran)
//执行成功,调用set修改状态
set(result);
}
} finally {
//判断任务是否被中断
int s = state;
if (s >= INTERRUPTING)
//调用中断处理逻辑
handlePossibleCancellationInterrupt(s);
}
}
线程call中方法正常执行完,调用set,
protected void set(V v) {
//将状态从初始化改为完成中
//如果CAS执行失败,有可能当前任务被取消或者中端,即调用了cancel方法
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
//将返回结果赋值给outcome
outcome = v;
//将状态从完成中改为已经完成
UNSAFE.putOrderedInt(this, stateOffset, NORMAL);
//将等待队列中的线程唤醒和清空
finishCompletion();
}
}
当状态为取消, 发生异常或者正常结束时,才会调用finishCompletion方法,用于将等待队列中的线程唤醒和清空。
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
//通过CAS将栈顶的引用指向null
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
//自选
for (;;) {
//当前等待节点中包含的线程
Thread t = q.thread;
//等待线程不为null
if (t != null) {
//当前节点的线程引用指向null
q.thread = null;
//当前节点包含的线程处于阻塞状态,唤醒该线程
LockSupport.unpark(t);
}
//当前等待节点的下一个节点
WaitNode next = q.next;
//下一个等待节点为空
if (next == null)
//结束自旋
break;
//将当前节点的next引用指向空
q.next = null; // unlink to help gc
//将下一个等待节点赋值给q
q = next;
}
break;
}
}
done();
//将callable的引用指向空
callable = null; // to reduce footprint
}
5 发生异常
FutrueTask类在run()方法中执行Callable中的call方法如果发生异常,会调用setException处理异常信息
protected void setException(Throwable t) {
//将状态修改为完成中
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
//将异常信息添加到返回结果
outcome = t;
//修改任务的执行状态,没有使用 UNSAFE的putIntVolatile方法而是采用putOrderedInt,
// 这是因为state状态使用volatile修饰,可以保证可见性,同时putOrderedInt方法的写入代价低于putIntVolatile
//通过CAS将完成中状态转换为异常状态
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL);
//将栈中等待的线程唤醒,清空栈
finishCompletion();
}
}
6 取消任务
取消任务使用cancel方法,入参mayInterruptIfRunning是布尔状态。
true表示使用中断逻辑,false使用取消逻辑。
public boolean cancel(boolean mayInterruptIfRunning) {
//校验状态,说明任务不可能进入Callable中的call方法,且最终状态可能要转换为中断/取消/正常完成这三种状态之一,
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try {
//tuew表示被中断
if (mayInterruptIfRunning) {
try {
//获取调用call方法的线程
Thread t = runner;
//判断线程是否为空
if (t != null)
//不为空则打上中断标记
t.interrupt();
} finally { // final state
//将状态修改为已中断
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
7 获取任务结果
获取任务结果使用get方法,这里以不带超时参数为例进行讲解
public V get() throws InterruptedException, ExecutionException {
int s = state;
//判断线程是否处于初始化状态
if (s <= COMPLETING)
//false表示不使用超时时间
s = awaitDone(false, 0L);
return report(s);
}
awaitDone是比较复杂逻辑,主要用get方法获取结果时,但call方法还在执行,可以让当前线程阻塞,从而不会占用CPU。
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
//获取终止时间,time为ture时才有用
final long deadline = timed ? System.nanoTime() + nanos : 0L;
//创建一个栈队列等待节点
WaitNode q = null;
//是否入栈标准,false表示还没有
boolean queued = false
//自旋
for (;;) {
//判断线程是否被标记为中断
//cancel方法入参为true时会给线程打上中断标志
if (Thread.interrupted()) {
//移除中断节点
removeWaiter(q);
//抛出中断异常,此时无法得到返回结果
throw new InterruptedException();
}
//将状态值赋予s
int s = state;
//说明任务正常执行完成/被取消/被中断,因此需要不需要再执行awaitDone方法
if (s > COMPLETING) {
if (q != null)
//help gc
q.thread = null;
return s;
}
//状态已经从初始化为变为完成中状态,但是最终状态还不确定,此时需要让出CPU,重新进入自旋
else if (s == COMPLETING)
Thread.yield();
//等待节点为空,装件
else if (q == null)
q = new WaitNode();
//等待节点还没入栈
else if (!queued)
//通过CAS将栈的头节点指向当前节点
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);
}
else
//将当前线程阻塞
//在没有使用超时时间的前提下,当前线程进入阻塞最少需要自旋3次
LockSupport.park(this);
}
}
将当前获取等待结果的线程从等待栈中移除
private void removeWaiter(WaitNode node) {
//校验节点是否为空
if (node != null) {
node.thread = null;
retry:
//自旋
for (;;) {
//当node是栈顶节点,q.thread == null, pred == null
执行CAS将node的下一个节点置为栈顶。
如果CAS失败,说明栈顶节点已经被替换为其他等待线程的节点
//当node非栈顶节点,将node的前驱节点的next'引用指向node的后继节点,释放node
for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
s = q.next;
if (q.thread != null)
pred = q;
else if (pred != null) {
pred.next = s;
if (pred.thread == null) // check for race
continue retry;
}
else if (!UNSAFE.compareAndSwapObject(this, waitersOffset,
q, s))
continue retry;
}
break;
}
}
}
8 参考文献
- JDK7在线文档
- JDK8在线文档
https://docs.oracle.com/javase/8/docs/api/ - Bruce Eckel,Java编程思想 第4版. 2007, 机械工业出版社