简介
Future是1.5版本引入的异步编程的顶层抽象接口,FutureTask则是Future的基础实现类。FutureTask同时还实现了Runnable接口,因此它即可以作为一个封装的任务单元,也可以作为一个独立的Runnable任务。今天主要通过FutureTask来开启异步编程的学习。
继承关系
说明
- FutureTask并不是直接实现Future接口的,而是通过RunnableFuture接口来间接实现- - RunnableFuture接口很简单,就是继承了Runnable和Future接口
- 最上层的FunctionalInterface是一个注解,拥有该注解的接口支持函数式编程,图中是Runnable接口拥有该注解
Future接口
public interface Future<V> {
// 任务取消方法
boolean cancel(boolean mayInterruptIfRunning);
// 判断任务是否取消
boolean isCancelled();
// 判断任务是否完成
boolean isDone();
// 阻塞获取任务结果
V get() throws InterruptedException, ExecutionException;
// 阻塞获取任务结果,并设置阻塞超时时间
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
RunnableFuture接口
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
属性
public class FutureTask<V> implements RunnableFuture<V> {
// 状态机:存在以下7中状态
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;
// 支持结果返回的任务
private Callable<V> callable;
// 任务执行结果:包含正常和异常的结果,通过get方法获取
private Object outcome;
// 任务执行线程
private volatile Thread runner;
// 栈结构的等待队列,该节点是栈中的最顶层节点
private volatile WaitNode waiters;
}
说明
- state状态机是FutureTask用于标记任务执行的状态情况,在源码中作者也描述了这些状态可能的变化请:
- 任务正常执行:NEW -> COMPLETING -> NORMAL
- 任务执行异常:NEW -> COMPLETING -> EXCEPTIONAL
- 任务被取消:NEW -> CANCELLE
- 任务被中断:NEW -> INTERRUPTING -> INTERRUPTED
- callable是FutureTask具体要执行的任务,由外部传入
- outcome是任务执行完后的返回结果
- runner是真正执行任务的worker
- waiters是一个等待节点,而是是最顶层节点,类似头节点。FutureTask中的等待队列主要作用用于当多线程同时请求get方法获取结果时,这些线程可能要同时阻塞,因此将这些线程的阻塞信息保存在了等待节点中,并构成一个栈的等待结构
上图展示了,Future任务状态的扭转图。new状态时通过调用set()方法、setException()方法可以使状态最终分别转变为NORMAL与EXCEPTIONAL; new状态时通过调用cancel(flase)方法、cancel(true)方法可以可以使状态最终分别转变为CANCELLED与INTERRUPTED。
再看一下用于保存由于调用Future.get方法而阻塞的线程的Treiber椎引用成变量waiters。
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
每当一个线程调用Future.get去获取任务的执行结果时,如果当前任务还没有执行结束、还没有被取消或者执行中未抛出异常。将产生一个新的WaitNode类型的节点,该节点持有调用Future.get方法的线程的引用,放入到Treiber椎中。FutureTask成变量waiters更新为最新的WaitNode节点。如下图。
构造方法
// 直接传入callable任务
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
// 传入runnable任务及结果变量
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
说明
-
FutureTask提供了两个构造方法,支持runnable和callable两种任务,但其实最终都是转换为callable任务
-
runnable转为callable的方法,其实就是通过RunnableAdapter适配器,RunnableAdapter本身是实现了callable接口,然后在call方法中,实际执行的是runnable的run方法,另外将传入的结果参数原封不动的作为结果返回。具体源码如下:
public static Callable callable(Runnable task, T result) { if (task == null) throw new NullPointerException(); return new RunnableAdapter(task, result); }
static final class RunnableAdapter implements Callable { final Runnable task; final T result; RunnableAdapter(Runnable task, T result) { this.task = task; this.result = result; } public T call() { task.run(); return result; } }
-
关于第二个构造方法的result的作用感觉还有点疑问,起码不能通过它来获取到任务的执行是否成功的,因为虽然在FutureTask中的run方法执行runnable抛出异常时,会捕获掉异常,但是在get方法中会根据状态机如果是非正常或取消时会抛出异常
主要方法
run()-任务执行
-
run方法是FutureTask任务实际执行体,它主要完成包装的callable的call方法执行,并将执行结果保存到outcome中,同时捕获了call方法执行出现的异常,并保存异常信息,而不是直接抛出。
-
另外,run方法存在的另一个意义就是通过它对状态机进行了维护,比如NEW-COMPLETEING-NORMAL 或 NEW-COMPLETEING-EXCEPTIONAL,保证了任务的处理流程
-
run方法一开始通过CAS更新runner为当前线程,从而避免了多线程下run被执行多次的问题。
public void run() { // 状态机不为NEW表示执行完成或任务被取消了,直接返回 // 状态机为NEW,同时将runner设置为当前线程,保证同一时刻只有一个线程执行run方法,如果设置失败也直接返回 if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return; try { Callable c = callable; // 取出任务检测不为空 且 再次检查状态为NEW(有点疑问为啥要重复检查?只是为了避免从开始到此处期间任务被取消的可能?) if (c != null && state == NEW) { V result; boolean ran; try { // 执行任务 result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; // 任务执行抛出异常时,保存异常信息,而不直接抛出 setException(ex); } // 执行成功则保存结果 if (ran) set(result); } } finally { // runner must be non-null until state is settled to // prevent concurrent calls to run() // 在次之前执行器必须不能null,避免多线程并发调用run()的情况 runner = null; // state must be re-read after nulling runner to prevent // leaked interrupts // 任务取消之后必须重新获取state的状态,防止错过处理中断请求 int s = state; // 如果被置为了中断状态则进行中断的处理 if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } }
get()-阻塞获取任务执行结果
-
get方法有两种,分别是一直阻塞和超时阻塞获取
-
get方法本意是直接获取任务执行结果,但是任务没执行完成时,会将当前线程进行阻塞等待,直到任务执行完成时才会唤醒。
-
请求线程阻塞时,会创建一个waiter节点,然后加入到阻塞等待的栈中。
-
当任务执行完成时或设置了阻塞超时时间的线程超时时,会将该线程从阻塞栈中移除,移除的方法很复杂,充分考虑了多线程并发的情况。
// 一直阻塞获取 public V get() throws InterruptedException, ExecutionException { int s = state; // 任务非最终完成状态前通过awaitDone方法进行阻塞等待 if (s <= COMPLETING) s = awaitDone(false, 0L); return report(s); }
// 超时阻塞获取 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); }
// 线程阻塞等待方法 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; if (s > COMPLETING) { // 任务已经完成时直接返回结果 if (q != null) q.thread = null; return s; } else if (s == COMPLETING) // 如果任务执行完成,但还差最后一步最终完成,则让出CPU给任务执行线程继续执行 Thread.yield(); else if (q == null) // 新进来的线程添加等待节点 q = new WaitNode(); else if (!queued) // 上一步节点创建完,还没将其添加到waiters栈中,因此在下一个循环就会执行此处进行入栈操作,并将当前线程的等待节点置于栈顶 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 // 非超时阻塞 LockSupport.park(this); }}
// 获取任务结果方法:正常执行则直接返回结果,否则抛出异常 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); }
从上面的分析中可以看出Future中任务没有执行完(包括正常执行、执行中抛出现异常、执行任务被取消)时,调用get方法的线程被LockSupport.park方法挂起,操作系统将不会对这个线程进行调度,当前线程被阻塞。而这个阻塞的结束是依靠调用LockSupport.unpark方法。LockSupport.unpark方法只有在任务正常执行完、执行中抛出现异常、执行任务被取消才会被调用。
cancel-任务取消
-
任务取消时会先检查是否允许取消,当任务已经完成或者正在完成(正常执行并继续处理结果 或 执行异常处理异常结果)时不允许取消。
-
cancel方法有个boolean入参,若为false,则只唤醒所有等待的线程,不中断正在执行的任务线程。若为true则直接中断任务执行线程,同时修改状态机为INTERRUPTED。
public boolean cancel(boolean mayInterruptIfRunning) { // 不允许取消的情况:状态机不是NEW 或CAS更新状态机失败 if (!(state == NEW && UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED))) return false; try { // in case call to interrupt throws exception // 如果要求中断执行中的任务,则直接中断任务执行线程,并更新状态机为最终状态INTERRUPTED if (mayInterruptIfRunning) { try { Thread t = runner; if (t != null) t.interrupt(); } finally { // final state UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED); } } } finally { finishCompletion(); } return true; }
其他方法
setException()-任务执行异常处理
setException方法主要用于任务执行异常对处理,主要完成异常信息保存到outcom结果、状态机从NEW到EXCEPTIONAL的变化更新,以及唤醒阻塞在waiters队列中请求get的所有线程
protected void setException(Throwable t) {
// 将状态机由NEW更新为COMPLETING
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
// 将异常信息保存到输出结果中
outcome = t;
// 更新状态机为处理异常的最终状态-EXCEPTIONAL
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
// 通用的完成操作,主要作用就是唤醒阻塞在waiters队列中请求get的线程,后面再详细
finishCompletion();
}
}
set()-任务正常执行处理
任务正常处理和异常处理流程基本一样,不一样的是状态的变化为NEW-COMPLETEING-NORMAL
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
复制代码
handlePossibleCancellationInterrupt()-处理可能的取消中断
这个方法名称很长,看起来比较模糊,其实它的作用就是——当发起中断的线程A将状态机更新为INTERRUPTING,还没继续中断任务任务线程前,CPU切换到任务执行线程B了,此时线程B执行本方法让出CPU,让发起中断的线程A能继续处理中断B的操作。(要结合cancel方法才能完全理解这句话)
private void handlePossibleCancellationInterrupt(int s) {
if (s == INTERRUPTING)
while (state == INTERRUPTING)
Thread.yield(); // wait out pending interrupt
}
removeWaiter()-移除等待节点
删除等待节点方法,同时清理因任务直接返回的线程等待节点,执行过程有点烧脑。主要是为了避免多线程并发更新的情况
private void removeWaiter(WaitNode node) {
if (node != null) {
node.thread = null;
retry:
for (;;) { // restart on removeWaiter race
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;
}
}
}
总结
到此为止,FutureTask的源码基本看完了,总的来说,FutureTask就是Future的最简单实现,基本就是把Future的接口方法实现了一遍,没有过多的功能拓展。简单总结下它的一些功能和特点吧:
- FutureTask实现了Runnable接口,因此可以作为一个线程执行任务处理,比如在线程池中submit方法就是用FutureTask类包装了一个runnable或callable任务。
- FutureTask内部有个状态机,用于记录任务的处理状态,比如有三种最终状态:正常完成、执行异常、任务取消
- 通过get方法阻塞获取任务执行结果,同时内部维护了一个阻塞等待栈,用于多线程并发调用get方法时,同时将这些线程阻塞并保存它们的阻塞信息,以便在任务执行完成后进行唤醒
- 支持任务的取消操作,但是前提是任务还没完全执行成功的情况下才允许取消,取消分为两种:只唤醒阻塞等待结果的线程、唤醒线程同时强制中断任务执行线程
作者:BucleLiu
链接:juejin.cn/post/684490…
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。