前言,最近在复习高并发的一些知识点,看到了FutureTask的时候,我感觉还是稍微有点复杂,于是多看几遍了,然后呈现如下的源码+例子。
FutureTask 的突出的功能点
- FutureTask 可以获得线程的执行结果;
- FutureTask 可以中断正在执行的线程
他们是怎么实现的呢? 我们带着问题往下分析吧。
一、FutureTask 结构
1.FutureTask 实现了 RunnableFuture 接口,而 RunnableFuture 继承自 Runnable 和 Future。看下 Future 代码如下:
public interface Future<V> {
boolean cancel(boolean var1);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long var1, TimeUnit var3) throws InterruptedException, ExecutionException, TimeoutException;
}
cancel(boolean mayInterruptIfRunning): 取消任务的执行,如果这个任务已经执行结束,或者已经被取消,或者不能被取消,这个方法就会执行失败并返回false;如果任务已经开始执行了,但是还没有执行结束,根据mayInterruptIfRunning的值,如果mayInterruptIfRunning = true,那么会中断执行任务的线程,然后返回true,如果参数为false,会返回true,不会中断执行任务的线程。
isCancelled(): 判断任务是否被取消;如果任务正常执行中被取消或者中断了则返回true;
isDone(): 判断任务是否执行结束,只要任务执行了,不管最后是执行成功或是任务取消了或是任务执行发生中断和异常都表示执行结束了。
V get(): 获取结果,如果这个计算任务还没有执行结束,该调用线程会进入阻塞状态。如果计算任务已经被取消,调用get()会抛出CancellationException,如果计算过程中抛出异常,该方法会抛出ExecutionException,如果当前线程在阻塞等待的时候被中断了,该方法会抛出InterruptedException。
V get(long timeout, TimeUnit unit): 带超时限制的get(),等待超时之后,该方法会抛出TimeoutException。等待未超时则会在任务完成时返回结果。
2. FutureTask 还包含了Callable 接口
public interface Callable<V> {
V call() throws Exception;
}
就是一个泛型接口,就是定义了一个call()方法,该方法有返回值,返回值的类型就是传入的泛型 。
二. FutureTask 相关变量。
1.执行状态
/**
* NEW -> COMPLETING -> NORMAL --------------------------------------- 正常执行结束的流程
* NEW -> COMPLETING -> EXCEPTIONAL ---------------------执行过程中出现异常的流程
* NEW -> CANCELLED -------------------------------------------被取消,即调用了cancel(false)
* NEW -> INTERRUPTING -> INTERRUPTED -------------被中断,即调用了cancel(true)
*/
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; //调用 cancel(false) 的状态
private static final int INTERRUPTING = 5; //调用 cancel(true) 的状态, 这是一个中间状态
private static final int INTERRUPTED = 6; //调用cancel(true)取消异步任务,会调用interrupt()中断线程的执行,然后状态会从INTERRUPTING变到INTERRUPTED
2. 变量
/**
* callable 任务结束后会置为null
*/
private Callable<V> callable; //
/**
* 用来保存计算任务的返回结果,或者执行过程中抛出的异常。
*/
private Object outcome; // non-volatile, protected by state reads/writes
/**
* 指向当前在运行Callable任务的线程
*/
private volatile Thread runner;
/**
* FutureTask的内部类,表示一个阻塞队列,如果任务还没有执行结束,那么调用get()获取结果的线程会阻塞,这些线程会放在这个队列中
*/
private volatile MyFutureTask.WaitNode waiters;
3. 一些CAS 的赋值操作
STATE = U.objectFieldOffset(FutureTask.class.getDeclaredField("state")); //state的偏移量,类似于指针,通过他来设置 state的值
RUNNER = U.objectFieldOffset(FutureTask.class.getDeclaredField("runner")); //同上
WAITERS = U.objectFieldOffset(FutureTask.class.getDeclaredField("waiters")); //同上
三、主要的API
1. 执行任务 run(): 为了加log,我修改了下 ,代码如下:
public void run() {
boolean isRunnerNull = U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread());
System.out.println("run state = " + state + ", isRunnerNull = " + isRunnerNull);
//如果 state != NEW 表示任务一开始执行了,直接返回
// 通过CAS 操作来判断变量 runner (RUNNER是指向 runner的指针)是否为null, 如果为null 则 将runner 赋值为当前线程并返回true ,如果不为null则返回false
if (state != NEW || !isRunnerNull) //注1
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {//执行 call()发生异常,比方说 空指针异常等
result = null;
ran = false;
setException(ex); //设置当前状态为异常
}
if (ran)//执行成功
set(result);// 注2 设置成功的状态
}
} 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)//处理中断, 里面的逻辑很简单,while 循环,如果是 INTERRUPTING 则调用 yield 让出当前线程
handlePossibleCancellationInterrupt(s);
}
}
我们写个Demo 来演示下操作,代码如下;
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("执行子线程操作 time " + System.currentTimeMillis());
Thread.sleep(3 * 1000);
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
System.out.println("sum = "+sum);
return sum;
}
}
public class TestDefineFutureTask {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyFutureTask<Integer> myFutureTask = new MyFutureTask<>(new MyCallable());
// setTaskThread(myFutureTask);
new Thread(myFutureTask)
.start();
}
/**
* 给FutureTask 的 runner 设置一个值
* @param myFutureTask
*/
private static void setTaskThread(MyFutureTask<Integer> myFutureTask) {
try {
Field field = myFutureTask.getClass().getDeclaredField("runner");
field.setAccessible(true);
field.set(myFutureTask, new Thread("hhh"));
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
以上就是调用 FutureTask 获取 1~99 的和,为了模拟耗时,设置睡眠3秒。执行结果如下:
run state = 0, isRunnerNull = true
执行子线程操作 time 1572861510987
sum = 4950
如果我们修改下上面的mian方法,如下:
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyFutureTask<Integer> myFutureTask = new MyFutureTask<>(new MyCallable());
setTaskThread(myFutureTask);//通过反射给MyFutureTask 的runner 设置值
new Thread(myFutureTask)
.start();
}
执行结果如下:
run state = 0, isRunnerNull = false
这个log就是上面的run方法中打印的,因为runner不为null 了,所以 isRunnerNull 为 false, 所以 run 方法中的 注1就直接返回了。
再看 run 方法中的 注2 ,也就是 set(Object obj) 方法;
protected void set(V v) {
if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {//判断当前的 状态是不是 NEW ,如果是则 设置state为 COMPLETING
outcome = v; //为返回结果赋值
U.putOrderedInt(this, STATE, NORMAL); //正常完成任务的最后状态
finishCompletion();
}
}
上面都注释了,就是如果是正常执行完成的话,会进入条件语句,最后为 state 设置为 NORMAL;然后执行 finishCompletion(),方法如下:
/**
* ----只用调用get方法的时候才会产生 等待线程----
* 删除并且通知所有等待结果的线程任务完成啦,可以返回结果了,
* 调用done() ,是一个 project方法,并且是空实现,用户可以自己实现该方法
* callable 置位 null
*/
private void finishCompletion() {
// assert state > COMPLETING;
System.out.println("finishCompletion waiters = "+waiters);
for (MyFutureTask.WaitNode q; (q = waiters) != null; ) {
System.out.println("finishCompletion q = "+q);
if (U.compareAndSwapObject(this, WAITERS, q, null)) {
for (; ; ) { //遍历通知且删除所有等待结果的线程
Thread t = q.thread;
System.out.println("finishCompletion t = "+t.getName());
if (t != null) {
q.thread = null;
LockSupport.unpark(t); //通知正在等待结果的线程任务执行完成了,可以返回结果啦。
}
MyFutureTask.WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
}
上面的方法也都注释了,强调一点,只有调用 get方法才会产生 等待结果的线程队列;
如果执行任务过程中发生异常,则执行 setException 方法,如下:
protected void setException(Throwable t) {
if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) { //如果当前的 state 是NEW ,则设置为 COMPLETING,
outcome = t; //get 返回的结果为 异常
U.putOrderedInt(this, STATE, EXCEPTIONAL); // 异常的最终状态为 EXCEPTIONAL
finishCompletion();
}
}
setException 的代码和 set 的基本差不多,不多说了。
2. 获取任务的执行结果 get()
get() 方法会阻塞当前的线程知道任务完成返回任务执行的结果。代码如下:
/**
* 获取任务执行的结果,会抛出 CancellationException 异常
*/
public V get() throws InterruptedException, ExecutionException {
int s = state;
System.out.println("get s = "+s);
if (s <= COMPLETING) //如果当前的状态是小于等于正在执行中的状态即任务没有完成,那么就阻塞住当前线程,否则直接 report 结果
s = awaitDone(false, 0L);
return report(s);
}
上面的代码很清楚,我们先看 简单的 report 方法,如下:
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL) //如果是任务完成状态,则直接返回任务执行结果
return (V) x;
if (s >= CANCELLED)//如果是任务取消了,则抛出 CancellationException
throw new CancellationException();
throw new ExecutionException((Throwable) x); //执行任务时发生了异常,抛出执行异常
}
再来看看稍微有点复杂的 awaitDone 方法:
/**
* 等待任务完成 或 任务取消 或 等待时间超时
* @param timed true 表示需要使用 timed 等待
* @param nanos 如果 timed 为true,需要等待的时间
* @return 返回状态或超时
*/
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
long startTime = 0L; // Special value 0L means not yet parked
MyFutureTask.WaitNode q = null;
boolean queued = false;
for (; ; ) {
int s = state;
System.out.println("awaitDone s = "+s+", q = "+q+", queued = "+queued);
if (s > COMPLETING) { //如果当前状态为 已完成 、取消或异常 则直接返回状态
if (q != null)
q.thread = null;
return s;
} else if (s == COMPLETING)// 表示任务结束(正常/异常),但是结果还没有保存到outcome字段,当前线程让出执行权,给其他线程先执行
Thread.yield();
else if (Thread.interrupted()) {//如果调用get()的线程被中断了,就从等待的线程栈中移除这个等待节点,然后抛出中断异常
removeWaiter(q);
throw new InterruptedException();
} else if (q == null) {// 如果等待节点q=null,就创建一个等待节点
boolean flag = timed && nanos <= 0L;
System.out.println("awaitDone s = "+s+", q = "+q+", flag = "+flag);
if (flag)
return s;
q = new MyFutureTask.WaitNode();
} else if (!queued)//如果这个等待节点还没有加入等待队列,就加入队列头
queued = U.compareAndSwapObject(this, WAITERS,
q.next = waiters, q);
else if (timed) { //如果设置了超时等待时间, 这个是调用 get(5, TimeUnit.SECONDS)才会走到这里
final long parkNanos;
if (startTime == 0L) { // first time
startTime = System.nanoTime();
if (startTime == 0L)
startTime = 1L;
parkNanos = nanos;
} else {
long elapsed = System.nanoTime() - startTime;
if (elapsed >= nanos) { //等待的时间比任务完成需要的时间短,则删除等待的线程
removeWaiter(q);//删除等待操作线程的具体实现
return state;
}
parkNanos = nanos - elapsed;
}
if (state < COMPLETING) { //等待的时间比任务完成需要的时间长,那么阻塞当前线程,等任务结束后唤醒该线程(LockSupport.unPark来唤醒)
LockSupport.parkNanos(this, parkNanos);
}
} else
LockSupport.park(this); //阻塞住,等待唤醒,这个是任务正常完成 或 取消 时会调用 LockSupport.unPark 操作来唤醒
}
}
该方法主要有几个执行点:
1.读取state,如果s > COMPLETING,表示任务已经执行结束,或者发生异常结束了,此时,调用get()的线程就不会阻塞;如果s == COMPLETING,表示任务结束(正常/异常),但是结果还没有保存到outcome字段,当前线程让出执行权,给其他线程先执行。
-
判断 Thread.interrupted() ;如果 执行了 cancel(true) 方法,那么 会调用线程的 interrupt(),所以这里的 Thread.interrupted() 为true, 会删除等待执行结果的线程并且抛出 InterruptedException ;
-
判断 q == null,如果等待节点q为null,就创建等待节点,这个节点后面会被插入阻塞队列
-
! queued , 如果这个等待节点还没有加入等待队列,就加入队列头。
-
if (timed) ,如果调用了 get(5, TimeUnit.SECONDS),会执行到这里,如果等待的时间比任务完成需要的时间短,则删除等待的线程,等待的时间比任务完成需要的时间长,那么阻塞当前线程,等任务结束后唤醒该线程(LockSupport.unPark来唤醒)
-
LockSupport.park(this) ,阻塞当前调用 get()方法的线程,直到任务完成或取消调用 finishCompletion() 来通知线程可以继续执行了。
get方法讲完了,下一个是 cancel() 。
3. 取消任务 cancel(boolean mayInterruptIfRunning)
代码如下:
public boolean cancel(boolean mayInterruptIfRunning) {
System.out.println("cancel state = "+state);
//判断state是否为NEW,如果不是NEW,说明任务已经结束或者被取消了,该方法会执行返回false
if (!(state == NEW &&
U.compareAndSwapInt(this, STATE, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED))) //设置 state 状态, 如果 mayInterruptIfRunning 为true 设置state 状态为INTERRUPTING
return false;
try { // in case call to interrupt throws exception
System.out.println("cancel ......mayInterruptIfRunning..."+mayInterruptIfRunning);
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt(); //中断正在执行任务的线程
} finally { // final state
U.putOrderedInt(this, STATE, INTERRUPTED); //设置 state 为 INTERRUPTED 即已中断状态
}
}
} finally {
finishCompletion();//唤醒等待返回结果的线程,释放资源
}
return true;
}
cancel 方法注释的也很清楚。
到此主要的方法都已经讲完了,欢迎指正!