一.FutureTask这个类
首先,很多人会发出这样的疑问,为什么会出现这个类?在java的发展历程中,不断的更新迭代,只为一个目的,解决问题!什么问题需要解决呢?异步计算,对,FutureTask的出现就是为了解决这个问题。光从字面上看,并没有体现出异步,确实也是如此,在使用该类的时候,要特别小心,要实现异步必须将FutureTask交给一个线程或线程池去执行。
1.类的继承实现关系
FutureTask实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnable接口和Future接口。这里有个疑问一直没弄明白,既然RunnableFuture中,并没有新的抽象方法,只有一个继承至Runnable的run方法,为什么FutureTask不直接实现Runnable和Futrue两个接口,非要搞个RunnableFuture出来,有知道的请在下方评论区留言告知,感激不尽!
2.设计思想
Doug Lea在设计很多并发工具的时候都采用了相似的思想,即AQS的设计思想。大致分为控制生命周期的状态State;存放等待线程的队列;贯穿整个state的修改和线程的出队入队的CAS,当然在用CAS的时候常常伴随着自旋和volatile。抓住了这些点,我们采用在阅读源码的时候以上帝的视角去审视每句代码的意图。
3.状态state
state总共有7个值NEW= 0、COMPLETING= 1、NORMAL= 2、EXCEPTIONAL= 3、CANCELLED= 4、INTERRUPTING =5、INTERRUPTED= 6。下面以图的形式展示显得直观些,并且还体现了状态的只被允许的转换路径
4.队列
该队列是线程安全的单链,节点为WaitNode,该类只有thread和next两个属性,thread用于存放等待结果的线程,在调用get方法时设置为当前线程。其实这个队列是一个stack,叫做Treiber stack,几十年前就提出来了。好早啊!没什么特别的,就是通过CAS来保证出栈和入栈线程安全的这么个stack。
5.属性
属性五个,执行状态state,任务对象callable,返回值outcome,执行任务的线程runner,等待结果的线程waiters。
6.构造方法
FutureTask(Callable<V> callable)和FutureTask(Runnable runnable, V result),构造器本身没什么说的,值得一说的是这里用到了一种设计模式。如何将在java1.0就已经设计好的runnable可以像用callable一样,作为参数传给futuretask,将两者合二为一呢?下面粘贴出FutureTask(Runnable runnable, V result)的源码以及相关的源码:
public FutureTask(Runnable runnable, V result) {
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);
}
//runnable--->RunnableAdapter<T>---->Callable<T>
//给runnable披上callable的外衣
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;
}
}
通过类名RunnableAdapter我们可以得知,答案就是适配器模式。但是我个人感觉,可能仔细的人也发现,这种实现很生硬,为什么要将传入的result作为返回值又返回呢?是不是很鸡肋。
7.主要方法
要想理解FutureTask是如何实现异步执行任务的关键,在于理解这几个方法:get()、run()、cancel()。从字面上大致理解一下,run()是执行任务的,肯定会调用callable的call方法,任务执行完设置返回值。get()异步获取执行结果,因为执行的run方法需要一定时间,所以该方法会阻塞线程,那么就会涉及到线程入队和出队的问题。cancel()取消方法的执行,叫取消不要准确,应该称为打断任务,打断了任务,等待结果的线程是不是也该做出反应。
二、源码分析
1.run()
前面已经大概介绍了run()方法,进入方法,首先是检查状态和设置runner,并且还保证了执行任务只有一个线程。然后就是拿到callable对象,调用call方法,如果执行正常结束,调用set(result)方法设置返回值,如果call方法内部抛出异常,调用setException()抛出异常。
public void run() {
//这一步,过滤作用和设置runner。过滤是指state不为NEW直接return,且
//一旦有线程已经进入run方法,其他调用run方法的线程进入将直接return。
//确保了只有一个线程执行run方法。
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
//再读取一遍state,因为可能被cancel
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()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
那么set(result)到底做了什么事?将state由NEW改为COMPLETING,这里说明一件事,COMPLETING并不是表示任务正在执行,而是表示任务已经执行完成,正在设置返回值。然后设置返回值,将state置为NORMAL最终态。这里有点不解,为什么不是CAS操作?Doug Lea给出的解释是:Transitions from these intermediate to final states use cheaper ordered/lazy writes because values are unique and cannot be further modified.大概意思是“因为值是唯一的且不会再被进一步修改,所以从中间态转变成最终态使用的是更低开销的顺序或者延迟写入。”回到代码中,接下来是执行finishCompletion(),介绍完setException()再一起说。
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
如果在执行call方法时出现异常,便会调用setException()方法,与set方法类似,只不过最终态不同,返回值是一个异常对象。
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
上面两个方法都调用了finishCompletion()方法,试想下,换成你来设计,你会在任务返回值都已经设置好了,接下来干什么?
没错,清空等待任务结果的线程队列,并唤醒他们。这里为什么有个for循环?目的是为了确保waiters不被并发修改。如果if判断waiters变了,就再执行循环一次,直到将waiters成功置为null为止。然后死循环里就是遍历waiters,将每个线程唤醒,并置为null,便于GC。done()是一个空的protected方法,子类可以实现该方法,用于在任务执行结束做出的动作。最后任务对象置为null。其实了解完FutureTask你会发现,该方法只有三个地方会用到,分别是set、setexception、cancel,也就是说不管task最后是以何种方式结束都是要唤醒每个线程的。
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
LockSupport.unpark(t);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
}
截止,run方法其实还没结束,回看run方法,finally中的代码还没执行。置空runner。检查状态,s >= INTERRUPTING其实就是 INTERRUPTING和INTERRUPTED。目的是检查是否被Cancel(true),如果没有执行完成操作,等待其执行完。
public void run() {
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 {
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()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
private void handlePossibleCancellationInterrupt(int s) {
// It is possible for our interrupter to stall before getting a
// chance to interrupt us. Let's spin-wait patiently.
if (s == INTERRUPTING)
while (state == INTERRUPTING)
Thread.yield(); // wait out pending interrupt
// We want to clear any interrupt we may have received from
// cancel(true). However, it is permissible to use interrupts
// as an independent mechanism for a task to communicate with
// its caller, and there is no way to clear only the
// cancellation interrupt.
}
至此,run方法就真的结束了。以下是run方法大致流程图:
2.get()方法
如果s <= COMPLETING说明返回值还没设置好,此时调用get()的线程进入等待队列。如果条件不满足,执行report(s),返回相应的值。
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
我们先来看report(s)方法,三种可能,正常返回、被取消、执行call时异常。
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);//执行时抛出异常,抛出执行异常
}
再来分析awaitDone方法,进入就是死循环,循环体先是判断当前线程即调用get的线程是否被打断,是,如果还没入队就直接抛出打断异常,如果已经入队,从队列中移出。往下,s > COMPLETING表示已经设置完返回值或者被取消,将thread置空,并返回。s == COMPLETING在set和setException方法被调用,表示正在 设置返回值,当前线程让出执行权。q为null表示还没生成节点,那么新建一个节点。!queued表示queued为false时,还没入队,将其入队,CAS操作。接下来是调用get(time)的时候,如果等待时间到了,就从队列中移出,没有就等待parknanos(nanos)。否则就park阻塞。线程被finishCompletion方法唤醒后继续执行下一次循环。
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) // cannot time out yet
Thread.yield();
else if (q == null)
q = new WaitNode();
else if (!queued)
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);
}
}
以下是get方法的一个流程图,相信这样更直观一点:
3、cancel()
从方法第一个条件可以看出,state为NEW才会执行下面的代码。不等于NEW,if判断中&&后面的CAS不会执行。如果等于NEW不管mayInterruptIfRunning是true还是false都会执行。简单来说,state不为NEW直接执行return false;state为NEW,
- mayInterruptIfRunning为true,State置为INTERRUPTING。
- mayInterruptIfRunning为false,state置为CANCELLED。
往下,判断mayInterruptIfRunning,为true,即state为INTERRUPTING,打断执行run的线程,并将state置为INTERRUPTED。
最后调用finishCompletion,置空队列,唤醒等待线程,置空callable。
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
以下就是cancel方法的大致流程图:
4.其他方法
FutureTask实现了runnable和Future接口,除了上面的方法外还有其他方法。
get()的超时版本:
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);
}
获取状态的方法:
public boolean isCancelled() {
return state >= CANCELLED;
}
public boolean isDone() {
return state != NEW;
}