引言
我们都知道Runable接口,它非常简单。但是它有一个问题——无法获取执行结果,以及一旦可以获取执行结果,什么时候可以获取执行结果?
public interface Runnable {
public abstract void run();
}
FutureTask就是为了解决这个问题的,将Runnable包装为FutureTask之后,就可以get获取任务执行结果,如果任务没有执行完,那么当前线程就会阻塞。
FutureTask确实非常好用,但我一直以来都比较好奇,将Runnable包装为FutureTask,为何就能实现执行结果的自动获取?或者换句话说,FutureTask的原理究竟是什么?
概述
我们先来概述一下整体的结构以及整体的设计。
FutureTask实现了RunnaleFuture接口,而后者则是继承自两个接口Future和Runnable,相当于两个接口的混合(接口支持多继承)。
事实上,Future接口也就定义了FutureTask类特性的交互协议。同样,它也非常简单。那么如何实现阻塞等待执行结果呢?
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;
}
考虑到多线程的环境——可能会有多个线程同时获取结果,一起阻塞,所以很自然地,我们需要将这些线程阻塞等待在一个列表之上,当任务执行完成之后,唤醒这些线程获取结果。
这也是FutureTask整体的设计思想,接下来,我们通过源码解析,探究它如何通过CAS的方式实现任务阻塞等待,以及维护等待队列。
源码解析
回调
Runnable是不返回结果的,所以首先,他会将Runnalbe执行和它关联的执行结果Result包装为带返回结果的Callable。这些都非常简单。
public interface Callable<V> {
V call() throws Exception;
}
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;
}
}
// FutureTask构造函数,将二者包装为callable
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
状态
FutureTask中设置了多种状态变量,用于标志任务执行的状态。理解源代码,首先需要理解这些状态的变化协议。
state有四种可能的状态转换:
- NEW -> COMPLETING -> NORMAL
- NEW -> COMPLETING -> EXCEPTIONAL
- NEW -> CANCELLED
- NEW -> INTERRUPTING -> INTERRUPTED
其中
- NEW为初始状态,任务执行过程也为该状态。
- COMPLETING 为中间状态,表示任务已经执行完,有线程正在调用set设置结果
- INTERRUPTING 为中间状态,表示正在中断中
- NORMAL,EXCEPTIONAL,CANCELLED,INTERRUPTED则为终结状态,分别表示正常结束,异常结束,任务取消,被中断
源码注释
有了上述知识,FutureTask的源码应该算比较简单,这里提供了核心代码的注释,从以下两个方面切入即可:
- run,任务执行,执行完调用set,set会调用finishCompletion唤醒所有等待线程
- get,获取结果,如果没有完成则阻塞当前线程插入队列
其次,FutureTask广泛使用了CAS,例如:
UNSAFE.compareAndSwapObject(this, runnerOffset