文章目录
Future接口
-
Future接口用来表示异步执行的结果,比如异步执行一个Callable任务就会返回一个Future对象,通过这个Future对象来获取异步执行的结果
-
继承关系如下:
一、Future
public interface Future<V> {
/**
* 1.尝试取消任务,如果任务已经完成或者被取消则会取消失败,也有可能因为其他原因取消失败,方法返回true表示取消成功,
* false表示取消失败,通常是因为任务已经开始了
* 2.如果任务还未开始并且被取消成功,那么这个任务再也不会被执行
* 3.如果任务已经开始,mayInterruptIfRunning参数决定执行该任务的线程是否需要中断任务,true表示正在执行任务的线程需要中断,false表示既然已经开始执行了,就执行完毕吧
* 4.如果方法返回true,那么isDone和isCancelled也会返回true
*/
boolean cancel(boolean mayInterruptIfRunning);
//如果任务在完成之前被取消,返回true
boolean isCancelled();
//如果任务已经完成,返回true,这里的完成不仅仅是代表正常完成,也有可能是异常终止,取消,这些都会返回true
boolean isDone();
//阻塞获取异步执行的结果
V get() throws InterruptedException, ExecutionException;
//指定超时时间的get超时
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
二、RunnableFuture
- RunnableFuture是Future的子接口,它继承了Future并且还继承了Runnable
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation unless it has been cancelled.
*/
void run();
}
- 我们看到其实RunnableFuture里面没什么东西,关键还是要看它的直接实现类FutureTask
三、FutureTask
- FutureTask是我们比较关心的类,它是RunnableFuture的直接实现类,由RunnableFuture接口我们知道,它具备Runnable和Future两种特性,因此FutureTask既可以作为Runnable执行,也可以作为Future获取执行结果。
- 我们知道异步可执行对象的接口是Callable,执行一个Callable会获得一个Future结果,FutureTask虽然可以代表Future对象,但是它没有实现Callable,只是实现了Runnable,其内部会将Callable转换为Runnable来执行,我们先看构造方法
3.1 构造方法
- FutureTask内部包装了一个Callable异步执行对象,构造的时候初始化任务对象并且设置初始任务状态
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);
}
//RunnableAdapter将Runnable转换为Callable对象,类很简单,就是实现了Callable对象,
//重写call方法,调用的是Runnable对象的run方法
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.2 任务状态和属性
3.2.1 任务状态
- FutureTask内部定义了一系列任务的运行状态用于表示任务的执行过程变化,代码中使用一个volatile的整型变量state来表示任务状态,一共有6种状态,如下:
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;
- 其中可能的状态变化如下:
NEW -> COMPLETING -> NORMAL
NEW -> COMPLETING -> EXCEPTIONAL
NEW -> CANCELLED
NEW -> INTERRUPTING -> INTERRUPTED
3.2.2 主要属性
//Callable任务对象
private Callable<V> callable;
//任务结果对象,不是volatile,但是通过任务的状态来保护它的可读性
private Object outcome; // non-volatile, protected by state reads/writes
//执行任务的线程对象
private volatile Thread runner;
//WaitNode对象栈,Treiber stack 是无锁栈
private volatile WaitNode waiters; /** Treiber stack of waiting threads */
3.3 任务主体方法
3.3.1 run执行任务
- run是执行任务的方法,继承自Runnable,里面本质是调用Callable的call,而我们前面提到过,call方法里面还是调用传入的Runnable任务对象的run方法
public void run() {
//1.设置当前运行任务的线程
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 {
//2.执行任务
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
//3.设置异常
setException(ex);
}
//4.设置结果
if (ran)
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
//5.设置状态,处理中断
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
3.3.2 set设置结果
- run成功执行完毕之后,会调用set方法将结果保存到outcome,并且通过CAS修改任务状态
protected void set(V v) {
//1.CAS设置状态为COMPLETING成功,则保存结果
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
//2.设置为最终状态
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
3.3.3 get获取结果
- get用于获取异步执行结果,我们看看源码细节,主要流程在awaitDone方法里面,后面解析awaitDone方法
public V get() throws InterruptedException, ExecutionException {
int s = state;
//1.获取状态,如果尚未完成,就等待直到完成
if (s <= COMPLETING)
s = awaitDone(false, 0L);
//2.返回结果
return report(s);
}
- 再看看超时模式,超时模式也不难,就是会判断awaitDone结束之后任务的状态,如果状态还是未完成的就抛出超时异常
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);
}
- report用于输出结果,如果状态是NORMAL就输出结果outcome,反之就抛出异常,这里我们可以看出,在整个过程中状态是很重要的
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);
}
3.3.4 cancel取消任务
- 取消任务的逻辑可以看注释,比较简单,注意mayInterruptIfRunning参数,这个参数为true表示任务即使已经开始了也需要中断,false则表示如果任务已经开始就不需要中断了。
public boolean cancel(boolean mayInterruptIfRunning) {
//1.根据mayInterruptIfRunning将状态从NEW修改为INTERRUPTING或者CANCELLED,修改失败就返回,
//mayInterruptIfRunning为true则将状态修改为INTERRUPTING,反之CANCELLED
if (!(state == NEW && UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
//mayInterruptIfRunning为true,需要中断线程
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
//2.清理工作
finishCompletion();
}
return true;
}
3.4 其他辅助方法
3.4.1 awaitDone阻塞
- awaitDone是阻塞等待获取结果的主要逻辑,在get的时候阻塞就是调用该方法,参数代表是否是超时模式
private int awaitDone(boolean timed, long nanos) throws InterruptedException {
//1.计算截止时间
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
//2.自旋等待
for (;;) {
//3.线程被中断,抛出异常,get方法是支持中断的,并且会移除已经中断的Waiter等待节点
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
//4.已经完成了,那就将线程置为null,返回线程状态
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
//5.COMPLETING状态表示FutureTask执行处于中间状态,线程让出CPU给任务线程执行任务
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
//6.构造对象
else if (q == null)
q = new WaitNode();
//7.设置waiter链表,这里的意思我猜是这样的,waiters代表了任务执行完毕之前,调用get获取结果的线程构成的一个waiterNode栈,
//代表当前线程的节点是q,这里把q设置为当前waiter的前驱,然后将waiters变量设置为q,假设有2个线程先后来get获取结果,任务都
//没有执行完毕,那么第二个线程对应的waiterNode就保存在waiter是变量里面,第一个线程就是第二个的后继,像一个链表连接起来
//WaitNode4(线程4) -> WaitNode3(线程3) -> WaitNode2(线程2) -> WaitNode1(线程1)
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
//8.如果是超时模式,就考虑是否超时,没有超时就使用LockSupport挂起线程
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
}
- awaitDone比较不好理解的是waiters这个结构,有这个结构是因为可能有多个线程来获取结果,比如线程1、2、3按照顺序来获取结果并且都阻塞了,那么对应1、2和3会产生3个WaiterNode对象,并且3的next后继是2,2的后继是1。这些线程在自旋的时候每次都会判断state状态,如果完成就退出,而最后退出后set设置结果的时候会调用finishCompletion,finishCompletion里面会处理这些WaiterNode节点。
3.4.2 finishCompletion
- 在取消任务、set设置完结果或者setException后会调用该方法,他会唤醒等待的线程并调用钩子方法done();
- 如果一个任务被取消,或者已经set了结果或者异常了,那么这个任务就相当于已经完成了(正常完成或者异常完成),此时需要唤醒调用get而被阻塞的线程(发生了异常的情况),或者执行完毕清理所有的WaiterNode节点(正常情况)
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
//1.将WaitNode置为null
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
//2.设置成功之后自旋
for (;;) {
//3.将线程对象置为null
Thread t = q.thread;
if (t != null) {
q.thread = null;
//4.唤醒线程继续执行,因为任务都被取消或者异常了,等待的线程就不能在继续傻等了,该起身走了
LockSupport.unpark(t);
}
//4.如果没有后继,就break
WaitNode next = q.next;
if (next == null)
break;
//5.如果有后继,就把前驱的next指针置为null(帮助gc),并处理后继节点
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
//6.调用钩子方法
done();
//7.成员变量置null
callable = null; // to reduce footprint
}
3.4.3 removeWaiter
- removeWaiter会尝试移除超时和已经中断的Waiter对象,代码就不解析了
3.5 内部类Waiter
- WaitNode是等待节点。waiters成员变量保存着调用get方法获取FutureTask结果的线程构成的一个栈,其实更像一个单向链表结构,当run方法没有执行完时,调用get方法的线程会形成一个阻塞的链表,waiters代表最近的获取结果的线程,如果先后有线程1到线程4来获取结果,那么形成的结构应该是
WaitNode4(线程4) -> WaitNode3(线程3) -> WaitNode2(线程2) -> WaitNode1(线程1)
- Waiter代码
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
3.6 扩展方法done
3.6.1 done
- done会在finishCompletion方法内部被调用,也就是说任务完成之后(正常或者异常,或者被取消都算完成),会唤醒等待结果而阻塞的全部线程,然后调用done来完成预定的工作,这是一个扩展接口。
protected void done() { }
3.6.2 done和CompletionService
- 在 线程池框架工具CompletionService 一文中介绍了CompletionService,CompletionService能够按照任务集中执行快慢,优先获取到先执行完毕的结果,里面利用的就是FutureTask的done这个扩展点,原理很简单,就是在执行完毕之后,在done方法里面讲结果加到一个阻塞队列里面去,然后先完毕的任务结果自然在队列前面,最后完成的任务结果就在队列的尾部,由此在获取结果的时候就能够按照任务的执行速度优先获取了,CompletionService里面的任务对象是FutureTask的子类,如下:
private final BlockingQueue<Future<V>> completionQueue;
private class QueueingFuture extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
//执行完毕之后,将Future结果对象加到阻塞队列中
protected void done() { completionQueue.add(task); }
private final Future<V> task;
}
- 更多可以阅读参考文章
四、小结
- 我们主要分析了FutureTask这个类,它即代表可执行的任务(Runnable),也代表获取结果的Future对象,构造的时候传入的Runnable会被包装成一个Callable,执行Callable的call实际上就是调用Runnable的run
- FutureTask内部通过集中状态来表示任务的执行过程中的变化,初始是NEW,在任务执行和结果获取的过程中都会依赖这个状态
- FutureTask里面需要引起我们注意的是其对多线程的支持,任务执行完毕之后可能有多个线程通过Future对象来get结果,在内部是通过一个WaitNode结果来表示多线程的,它内部包装了线程对象,同时通过链表的结构组织起来。FutureTask在get获取结果的时候会阻塞,一直自旋直到完成或者被中断,或者超时,自旋过程中会不断的读取当前的state状态,state的volatile的保证线程间的可见性,这个waiterNode结构在线程任务结束之后会调用finishCompletion来清理掉,或者让阻塞的线程继续执行。