概述
FutureTask的使用场景:FutureTask可用于异步获取执行结果或取消执行任务的场景。通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过FutureTask的get
方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。另外,FutureTask还可以确保即使调用了多次run方法,它都只会执行一次Runnable或者Callable任务,或者通过cancel取消FutureTask的执行等。
在JDK1.5时,增加了一种开启线程的方式,实现Callable接口,这种开启线程的方式可以拿到返回值
并且可以捕获异常
。但是我们必须配合着FutureTask类使用。FutureTask类上就是Runnable接口的一个实现类。因为最终开启线程还是得通过new Thread().start()
的方式开启线程。
开启线程的简单方式
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);
}
接下来我们来分析一下FuturetTask的源码!!! 为接下来的线程池源码解析打好基础
1.属性
public class FutureTask<V> implements RunnableFuture<V> {
//表示当前任务的状态
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(); }
}
2.构造方法
2.1传入一个Callable接口的实现类
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
//为callable属性赋值
this.callable = callable;
//设置当前任务状态为New
this.state = NEW;
}
2.2传入一个Runnable接口实现类
public FutureTask(Runnable runnable, V result) {
/*
* 使用适配器模式将runnable转换为了callable。
* 底层实际上就是创建了Callable接口的实现类,在call方法里调用了传的runnable的run方法,然后将我们传入的result原生不动的返回了。
*
* 为内部的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;
}
}
2.3总结
FutureTask的构造器主要就是传入一个Callable或者Runnable
的实现类,为内部的callable属性赋值,当我们传入的是Runnable时,会将我们的Runnable进行包装,包装成一个Callable,返回值我们自定义(返回值没有意义)。
3.run()方法
大致流程解析
-
先判断任务的状态是否处于NEW的状态,不处于NEW的状态直接返回不执行。
-
当前线程尝试使用
CAS修改内部的runner的引用
的方式去执行这个任务,CAS失败说明其他线程抢到了这个任务,当前线程也直接返回。 -
当前线程如果尝试
CAS修改runner指向为当前线程
成功就会去执行任务。 -
如果任务正常执行(没有出现异常),会拿到执行结果,并且调用set()方法,将结果设置到FutureTask内部的outcome属性中,并将线程状态修改为
NORMAL(正常结束)
,set()方法调用finishCompletion()方法将所有的读线程全部唤醒,最后将FutureTask内部的callable置为NULL。 -
如果任务执行过程中出现了异常,首先将执行结果置为NULL,然后调用setException()方法,先将异常信息设置到outcome中,然后将任务状态设置为EXCEPTIONAL,最终调用finishCompletion方法将读线程全部唤醒。
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<V> c = callable;
/*
* 判断 callable不为NULL,并且state == NEW(这里是为了防止外部线程cancel掉当前任务)
*/
if (c != null && state == NEW) {
//保留结果的引用
V result;
//true表示callable.run代码块执行成功,未抛出异常
boolean ran;
try {
//调用程序员自定义的callable或者适配之后的runnable拿到结果
result = c.call();
//执行过程中未抛出异常,将ran置为true。
ran = true;
} catch (Throwable ex) {
//抛出异常 将结果置为NULL
result = null;
//ran置为false
ran = false;
//后面讲
setException(ex);
}
//运行成功
if (ran)
//CAS方式设置任务的状态并将结果设置到outcome中
set(result);
}
} finally {
//将runner置为NULL。
runner = null;
//关于中断。。后面讲
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
4.set()方法
/*
* 使用CAS的方式设置任务状态并将结果写入到outcome中。
*/
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
//结果赋值给outcome
outcome = v;
//修改任务状态为NORMAL(非cAS,直接操作内存进行写的过程)。
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
//后面讲
finishCompletion();
}
}
5.setException()方法
protected void setException(Throwable t) {
/*
* 使用CAS方式设置当前任务状态为 完成中。。。
*/
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
//这里的t就是异常对象 赋值给outcome
outcome = t;
//修改任务状态为EXCEPTIONAL 即出现异常的。
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL);
finishCompletion();
}
}
6.get()方法
大致流程
先获取任务的状态state,如果说任务处于NEW或者COMPLETING(未完全完成)
,会进入awaitDone()方法进行挂起。否则说明任务已完成,调用report()方法,取出run方法完成时向outcome写入的结果或者异常信息进行返回,可能抛出异常。
/*
* 场景:多个线程等待当前任务执行完成后的结果
*/
public V get() throws InterruptedException, ExecutionException {
int s = state;
/*
* 条件成立: 当前任务未执行、正在执行、正完成
* 调用get()的外部线程(非runner线程) 会被阻塞在get()方法上
*/
if (s <= COMPLETING)
//返回当前任务的状态,如果内部抛出中断异常,get()方法也会向上抛。
s = awaitDone(false, 0L);
//返回结果
return report(s);
}
7.awaitDone()方法
整个方法是一个自旋
(下面的第一次第二次第三次的前提条件时任务状态没有完成并且不处于接近完成时)
- 1.第一次进入会将当前线程构造成一个WaitNode节点
- 2.第二次进入会将构造出来的节点入栈
(头插头出的队列)
- 3.第三次进入会调用LockSupport当前线程挂起。
- 4.当当前线程被唤醒时,首先会判断唤醒的原因是否是因为其他线程对当前线程进行了中断操作,如果是的话,
就将当前线程对应的WaitNode节点出栈
,然后抛出中断异常。 - 5.判断当任务状态处于接近完成的状态,那么就释放CPU,然后进入下一次的抢占过程中。
- 6.当任务处于已完成时,将WaitNode中的线程属性设置为NULL,最终返回任务的最终状态
state
。
/*
* 返回值是任务的状态。
*/
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
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);
//抛出中断异常
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){
//当前线程node节点,next指向原队列的头节点,waiters一直指向队列的头
q.next = waiters;
//CAS尝试将waiters指向当前的头结点。
queued = UNSAFE.compareAndSwapObject(this, waitersOffset, 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);
}
}
8.removeWaiter()方法
private void removeWaiter(WaitNode node) {
if (node != null) {
//将node内部的thread置为NULL。
node.thread = null;
//自旋
retry:
for (;;) {
/*
* 遍历链表,将指定节点删除,可能有些节点因为中断没有出队,但是内部的thread被设置为NULL了(见上面的方法),
* 也将这些节点一起出队。
*/
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;
}
}
}
9.report()方法
去获取run()方法结束后存储在outcome中的结果并返回
private V report(int s) throws ExecutionException {
/*
* 正常情况下,outcome保存的是callable运行结束的结果
* 非正常情况下,outcome保存的是callable抛出的异常对象
*/
Object x = outcome;
//条件成立:当前任务状态正常结束
if (s == NORMAL)
//直接返回结果
return (V)x;
//任务被结束
if (s >= CANCELLED)
//抛出取消异常
throw new CancellationException();
//除了正常情况,抛出异常
throw new ExecutionException((Throwable)x);
}
10.finishCompletion()
大致流程就是遍历队列中的所有WaitNode节点,将所有等待的线程全部唤醒,并将所有的WaitNode设置为NULL。
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();
//将callable设置为NULL help GC。
callable = null; // to reduce footprint
}
11.cancel()方法
public boolean cancel(boolean mayInterruptIfRunning) {
/*
* CAS操作。尝试修改线程状态。
* 尝试修改为中断或者是取消(看传来的参数)
*/
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try {
//true,将线程设置为中断状态。
if (mayInterruptIfRunning) {
try {
//执行任务的线程 有可能现在是NULL。
//NULL的情况就是:当前任务还在队列中,还没有线程去获取它
Thread t = runner;
if (t != null)
/*
* 调用线程的中断方法,如果程序时响应中断的话,会走中断逻辑
* 假设程序不是响应中断的,那么啥也不会发生。
*/
t.interrupt();
} finally {
// final state
//中断完成后将状态改为中断完成
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
//调用 finishCompletion() 完成线程。会唤醒所有get阻塞的线程。
finishCompletion();
}
return true;
}