线程池—FutureTask源码解析
简介
-
在学习线程池之前,需要先学习一下 FutureTask,因为线程池 submit 的结果就是 FutureTask。
-
那么什么是 FutureTask,其表示一个未来的任务。也就是说这个任务比较耗时,当前调用线程会阻塞拿到这个结果。
-
FutureTask 接口继承体系
-
FutureTask 实现了 Future 和 Runnable 接口,又能执行,又能拿到执行结果。
-
所有的通过 submit 方式提交到线程池的任务(Runnable、Callable),进入到队列或被线程执行之前,都会被封装成 FutureTask,然后提交到线程池中。
Future 接口
-
Future 接口表示异步计算的结果,通过 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; }
Runnable 接口,就不做过多的解释了。
FutureTask 的使用场景
- FutureTask 可用于异步获取执行结果或取消执行任务的场景。通过传入 Runnable 或者 Callable 的任务给 FutureTask,直接调用其
run
方法或者放入线程池执行,之后可以在外部通过 FutureTask 的get
方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。另外,FutureTask 还可以确保即使调用了多次run
方法,它都只会执行一次 Runnable 或者 Callable 任务,或者通过cancel
取消 FutureTask 的执行等。 - 关于 FutureTask 的基本使用可以参考文章:FutureTask的用法
在 JDK1.5 时,增加了一种开启线程的方式,实现 Callable 接口,这种开启线程的方式可以拿到返回值
并且可以捕获异常
。但是我们必须配合着FutureTask 类使用。FutureTask 类上就是 Runnable 接口的一个实现类。因为最终开启线程还是得通过new Thread().start()
的方式开启线程。
线程池返回的 Future
-
简单的线程池案例
public class FutureTaskDemo { public static void main(String[] args) throws Exception{ ExecutorService threadPool = Executors.newFixedThreadPool(5); Future<?> future = threadPool.submit(new Runnable() { @Override public void run() { System.out.println("FutureTaskDemo.run"); } }); future.get(); } }
-
我们可以看到线程池返回的 Future 是
FutureTask
:
开启线程的简单方式
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 匿名类的方式构造一个Callable的实现类
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 1;
}
};
// 将Callable的实现类传入
FutureTask<Integer> task = new FutureTask<>(callable);
// FutureTask就是Runnable的实现类,直接传到构造函数中即可
Thread thread = new Thread(task);
// 开启线程
thread.start();
// 阻塞拿到返回值
int x = task.get();
System.out.println(x);
}
接下来我们来分析一下 FutureTask 的源码,为接下来的线程池源码解析打好基础!
FutureTask 源码
属性
public class FutureTask<V> implements RunnableFuture<V> {
// 表示当前task任务的状态
private volatile int state;
// 新建状态,当前任务尚未执行
private static final int NEW = 0;
// 当前任务正在结束,尚未完全结束,一种临界状态
private static final int COMPLETING = 1;
// 当前任务正常结束
private static final int NORMAL = 2;
// 当前任务执行过程中发生了异常,内部封装的callable.run() 向上抛出了异常
private static final int EXCEPTIONAL = 3;
// 当前任务被取消
private static final int CANCELLED = 4;
// 当前任务中断中
private static final int INTERRUPTING = 5;
// 当前任务已中断
private static final int INTERRUPTED = 6;
/*
* 线程池调用submit()方法时将Runnable变为Callable(适配器模式)
*/
private Callable<V> callable;
/*
* 正常情况下:任务执行结束,outcome保存执行结果(callable的返回值)
* 非正常情况:callable向上抛出异常,outcome保存异常
*/
private Object outcome;
// 当前任务被线程执行期间,保存当前执行任务的线程引用
private volatile Thread runner;
// 因为会有很多线程去get当前任务的结果,所以这里使用了一种Stack的数据结构来存储线程
private volatile WaitNode waiters;
// WaitNode节点
static final class WaitNode {
// 引用当前线程
volatile Thread thread;
// 下一个节点
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
构造方法
-
传入一个
Callable
接口的实现类public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); // 为callable属性赋值 这里的callable就是程序员自己实现的业务类 this.callable = callable; // 设置当前任务状态为NEW(0) this.state = NEW; }
-
传入一个
Runnable
接口的实现类public FutureTask(Runnable runnable, V result) { /* * 使用适配器模式将runnable转换为了callable 外部线程 通过get获取当前任务执行结果时,结果可能为 null 也可能为 传进来的值 * (submit的重载方法中有一个只传入Runnable的方法,而它的result就为null:RunnableFuture<Void> ftask = newTaskFor(task, null);) * 底层实际上就是创建了Callable接口的实现类,在call方法里调用了传的runnable的run方法,然后将我们传入的result原生不动的返回了 * RunnableAdapter 通过将 Runnable 组合到自身内部,再实现 Callable 接口来完成从 Runnable 到 Callable 的适配 */ // 为内部的callable属性赋值 this.callable = Executors.callable(runnable, result); // 设置任务为初始状态 this.state = NEW; } // || // || // \/ 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() { // 直接运行了run方法 task.run(); // 直接将传来的result返回了 传什么返回什么 return result; } }
-
总结
FutureTask 的构造器主要就是传入一个
Callable 或者 Runnable
的实现类,为内部的 callable 属性赋值,当我们传入的是 Runnable 时,会将我们的 Runnable 通过适配器模式
进行包装,包装成一个 Callable,返回值我们自定义(返回值没有意义)。
run()
大致流程解析
- 先判断任务的状态是否处于
NEW
的状态,不处于NEW
的状态直接返回不执行。 - 当前线程尝试使用
CAS修改内部的runner的引用
的方式去执行这个任务,CAS 失败说明其他线程抢到了这个任务,当前线程也直接返回。 - 当前线程如果尝试
CAS修改runner指向为当前线程
成功就会去执行任务。 - 如果任务正常执行(没有出现异常),会拿到执行结果,并且调用 set() 方法,将结果设置到 FutureTask 内部的 outcome 属性中,并将线程状态修改为
NORMAL(正常结束)
,set() 方法调用 finishCompletion() 方法将所有的读线程全部唤醒,最后将 FutureTask 内部的 callable 置为 null。 - 如果任务执行过程中出现了异常,首先将执行结果置为 null,然后调用 setException() 方法,先将异常信息设置到 outcome 中,然后将任务状态设置为 EXCEPTIONAL,最终调用 finishCompletion 方法将读线程全部唤醒。
// submit(runnable/callable) -> newTaskFor(runnable) -> execute(task) -> pool
// 任务执行入口
public void run() {
/*
* 1.先判断state是否处于NEW状态,非NEW状态的任务线程都不会执行
* 2.尝试使用CAS更新内部的runner(Thread)属性为当前线程,CAS失败表示有其他线程竞争这个任务
*/
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
/*
* 执行到这里表示当前任务一定是NEW状态 并且 当前线程也抢占任务成功
*/
try {
// 内部的callable为c赋值 就是程序员自己封装逻辑的callable 或者 装饰后的runnable
Callable<V> c = callable;
/*
* 条件1:c != null 防止空指针异常
* 条件2:state == NEW 防止外部线程 cancel掉当前任务
*/
if (c != null && state == NEW) {
// 保留结果的引用
V result;
// true 表示callable.run 代码块执行成功 未抛出异常
// false 表示callable.run 代码块执行失败 抛出异常
boolean ran;
try {
// 调用程序员自定义的callable或者适配之后的runnable拿到结果
result = c.call();
// c.call未抛出任何异常,ran会设置为true 代码块执行成功
ran = true;
} catch (Throwable ex) {
// 说明程序员自己写的逻辑块有bug了 抛出异常 将结果置为null
result = null;
// ran置为false
ran = false;
// 后面讲
// 非正常情况下 有异常 会执行这个set方法
setException(ex);
}
// 运行成功
if (ran)
// 说明当前c.call正常执行结束了
// set就是用CAS方式设置任务的状态并将结果设置到outcome中
set(result);
}
} finally {
// 将当前线程的引用runner置为null
runner = null;
// 关于中断..后面讲
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
set()
/*
* 使用CAS的方式设置任务状态并将结果写入到outcome中
*/
protected void set(V v) {
// 使用CAS方式设置当前任务状态为 COMPLETING(1)完成中..
// 有没有可能失败呢?外部线程等不及了,直接在set执行CAS之前 将task取消了(很小概率事件)
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
// 结果赋值给outcome
outcome = v;
// 将结果赋值给outcome之后,马上会将任务状态修改为NORMAL(2)正常结束状态(非cAS,直接操作内存进行写的过程)
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
// 后面讲
// 猜一猜?最起码得把get()在此阻塞的线程 唤醒..
finishCompletion();
}
}
setException()
protected void setException(Throwable t) {
/*
* 使用CAS方式设置当前任务状态为 COMPLETING(1)完成中..
*/
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
// 这里的t引用的就是 callable 向上层抛出来的异常,赋值给outcome
outcome = t;
// 修改任务状态为EXCEPTIONAL(3),即出现异常的状态
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL);
// 讲完get()再说
finishCompletion();
}
}
get()
大致流程
- 先获取任务的状态 state,如果说任务处于
NEW 或者 COMPLETING(未完全完成)
,会进入 awaitDone() 方法进行挂起。否则说明任务已完成,调用 report() 方法,取出 run 方法完成时向 outcome 写入的结果或者异常信息进行返回,可能抛出异常。
/*
* 场景:多个线程等待当前任务执行完成后的结果
*/
public V get() throws InterruptedException, ExecutionException {
int s = state;
/*
* 条件成立:当前任务未执行、正在执行、正完成
* 调用get()的外部线程(非runner线程)会被阻塞在get()方法上
*/
if (s <= COMPLETING)
// 返回当前任务的状态(可能当前线程已经在里面睡了一会了..),如果内部抛出中断异常,get()方法也会向上抛
// awaitDone 实现阻塞的方法 FutureTask最核心的地方
s = awaitDone(false, 0L);
// 返回结果
return report(s);
}
awaitDone()
整个方法是一个自旋
(下面的第一次第二次第三次的前提条件时任务状态没有完成并且不处于接近完成时)
- 第一次进入会将当前线程构造成一个 WaitNode 节点。
- 第二次进入会将构造出来的节点入栈
(头插头出的队列)
。 - 第三次进入会调用 LockSupport 当前线程挂起。
- 当当前线程被唤醒时,首先会判断唤醒的原因是否是因为其他线程对当前线程进行了中断操作,如果是的话,
就将当前线程对应的WaitNode节点出栈
,然后抛出中断异常。 - 判断当任务状态处于接近完成的状态,那么就释放 CPU,然后进入下一次的抢占过程中。
- 当任务处于已完成时,将 WaitNode 中的线程属性设置为 null,最终返回任务的最终状态
state
。
/*
* 返回值是任务的状态
*/
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
// 假设此次是不带超时的方法 拿到的时间为0
final long deadline = timed ? System.nanoTime() + nanos : 0L;
// 引用当前线程 封装成WaitNode对象
WaitNode q = null;
// 表示当前线程WaitNode对象有没有入队/压栈
boolean queued = false;
// 自旋
for (;;) {
/*
* 进入这个if 说明当前线程已经被阻塞后唤醒了,并且是被其他线程使用中断的方式唤醒的,
* interrupted()返回true后会将Thread的中断标记重置为false(即下一次再调用此方法时就会返回false)
*/
if (Thread.interrupted()) {
// 将Node节点出栈
removeWaiter(q);
// get方法抛出中断异常
throw new InterruptedException();
}
/*
* 线程被阻塞(挂起)后,被其他线程使用unpark()正常唤醒,会正常自旋,走下面逻辑
*/
// 读取当前任务的最新状态
int s = state;
// 条件成立:表示当前任务已经有结果了.. 可能是好 可能是坏
if (s > COMPLETING) {
// 条件成立:说明已经为当前线程创建过node了,此时需要将node.thread = null
if (q != null)
q.thread = null; // help GC
return s; // 返回当前状态
}
// 条件成立:说明任务接近完成状态,这里让当前线程释放CPU,进入下一次抢占CPU的状态
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
/*
* 第一次自旋:当前线程还未创建WaitNode对象,此时根据当前线程封装一个WaitNode对象
*/
else if (q == null)
q = new WaitNode();
/*
* 第二次自旋:WaitNode对象已经创建,但是node对象还未入队
*/
else if (!queued)
// q.next = waiters 当前线程node节点 next指向原队列的头节点(waiters一直指向队列的头)
// CAS方式设置waiters引用指向 当前线程node(队列头结点),成功的话 queued == true 否则,可能其它线程先你一步入队了
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
// 当前get()操作的线程就会被park掉(阻塞) 线程状态变为WAITING状态 相当于休眠了
// 除非有其他线程将你唤醒 或者 将当前线程中断
LockSupport.park(this);
}
}
removeWaiter()
private void removeWaiter(WaitNode node) {
if (node != null) {
// 将node内部的thread置为null
node.thread = null;
// 自旋
retry:
for (;;) {
/*
* 遍历链表,将指定节点删除,可能有些节点因为中断没有出队,但是内部的thread被设置为null了(见上面的方法)
* 也将这些节点一起出队
*/
/*
* pred 当前节点的前一个节点
* q 当前节点
* s 当前节点的下一个节点
* 循环条件为:当前节点不为空
* 每次将q指向下一位
*/
for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
s = q.next; // 赋值
if (q.thread != null)
pred = q; // pred指向q 开始下一轮循环
// 前置条件:当前节点为null
// 条件成立:说明当前节点不是头结点
else if (pred != null) {
// 将前置节点的指针指向下一个节点 相当于删除当前节点
pred.next = s;
// 如果前置节点此时也为空 直接continue再次进行自旋
if (pred.thread == null) // check for race
continue retry;
}
// 说明当前节点是头结点 进行CAS替换操作 将waiters当前节点的下一个节点
else if (!UNSAFE.compareAndSwapObject(this, waitersOffset,
q, s))
continue retry;
}
break; // 全部处理完则退出自旋
}
}
}
report()
- 去获取 run() 方法结束后存储在
outcome
中的结果并返回
/*
* 处理 get 方法的返回结果
*/
private V report(int s) throws ExecutionException {
/*
* 正常情况下,outcome保存的是callable运行结束的结果
* 非正常情况下,outcome保存的是callable抛出的异常对象
*/
Object x = outcome;
// 条件成立:当前任务状态正常结束
if (s == NORMAL)
// 直接返回callable运算结果
return (V)x;
// 任务被结束
if (s >= CANCELLED)
// 抛出取消异常
throw new CancellationException();
// 执行到这,说明callable接口实现中,是有bug的.. 抛出异常
throw new ExecutionException((Throwable)x);
}
finishCompletion()
- 大致流程就是遍历队列中的所有 WaitNode 节点,将所有等待的线程全部唤醒,并将所有的 WaitNode 设置为null。
/*
* 在 set 方法中调用该方法
*/
private void finishCompletion() {
// q指向waiters链表的头结点
for (WaitNode q; (q = waiters) != null;) {
/*
* 使用CAS设置waiters为null,是因为怕外部线程使用cancel取消当前任务也会触发finishCompletion(小概率事件)
*/
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
// 自旋
for (;;) {
// 获取当前node节点封装的thread
Thread t = q.thread;
// 条件成立,说明当前线程不为null
if (t != null) {
q.thread = null; // help GC
// 唤醒当前节点对应的线程
LockSupport.unpark(t);
}
// next 当前节点的下一个节点
WaitNode next = q.next;
// 说明当前的节点时最后一个节点
if (next == null)
break; // 说明都唤醒完成 直接退出自旋
q.next = null; // help gc
q = next; // 指向下一个节点
}
break; // 此时也说明全部处理完成 跳出最外层自旋
}
}
// done接口 里面什么都没有实现 是一个可以进行扩展的方法
done();
// 将callable设置为null help GC
callable = null; // to reduce footprint
}
cancel()
- cancel 方法也会调用 finishCompletion 方法。
/*
* 如果通过线程池submit提交了任务之后,这个任务迟迟没有结束,这时在外部可以通过future句柄调用它的 cancel 方法,尝试中断/取消当前任务
* @param:mayInterruptIfRunning
* true->如果当前任务处于正在运行状态,会给线程发一个中断信号
* false->直接改变当线程状态
* @return:cancel成功则返回true 否则返回false
*/
public boolean cancel(boolean mayInterruptIfRunning) {
/*
* 条件1:state == NEW 成立,表示当前任务处于运行中 或者 处于线程池 任务队列中..
* 条件2:UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED))
* CAS操作 尝试修改线程状态为中断或者是取消(看传来的参数)
* 条件成立:说明修改状态成功,可以去执行下面逻辑了,否则 返回false 表示cancel失败
*/
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try {
// true,将线程设置为中断状态
if (mayInterruptIfRunning) {
try {
// 执行任务的线程 有可能现在是null
// null的情况就是:当前任务还在队列中,还没有线程去获取它
Thread t = runner;
// 条件成立:说明当前线程runner正在执行task
if (t != null)
/*
* 调用线程的中断方法,如果程序是响应中断的话,会走中断逻辑
* 假设程序不是响应中断的,那么啥也不会发生
*/
t.interrupt();
} finally { // final state
// 中断完成后将状态改为中断完成
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
// 调用 finishCompletion() 完成线程 会唤醒所有get阻塞的线程
finishCompletion();
}
return true;
}
参考