1. 概念
FutureTask一个可取消的异步计算,FutureTask 实现了Future的基本方法,提供 start cancel 操作,可以查询计算是否已经完成,并且可以获取计算的结果。结果只可以在计算完成之后获取,get方法会阻塞当计算没有完成的时候,一旦计算已经完成,那么计算就不能再次启动或是取消。
2. 使用场景
FutureTask可用于异步获取执行结果或取消执行任务的场景。通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过FutureTask的get方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。另外,FutureTask还可以确保即使调用了多次run方法,它都只会执行一次Runnable或者Callable任务,或者通过cancel取消FutureTask的执行等。
public class FutureTest1 {
public static void main(String[] args) {
Task task = new Task();// 新建异步任务
FutureTask<Integer> future = new FutureTask<Integer>(task) {
// 异步任务执行完成,回调
@Override
protected void done() {
try {
System.out.println("future.done():" + get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
};
// 创建线程池(使用了预定义的配置)
ExecutorService executor = Executors.newCachedThreadPool();
executor.execute(future);
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
// 可以取消异步任务
// future.cancel(true);
try {
// 阻塞,等待异步任务执行完毕-获取异步任务的返回值
System.out.println("future.get():" + future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
// 异步任务
static class Task implements Callable<Integer> {
// 返回异步任务的执行结果
@Override
public Integer call() throws Exception {
int i = 0;
for (; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName() + "_"
+ i);
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return i;
}
}
}
3. 代码分析
Runnable这个接口我们都知道,那剩下的就是来看看Future这个接口了:
public interface Future<V> {
//取消任务,如果任务正在运行的,mayInterruptIfRunning为true时,表明这个任务会被打断的,并返回true;
// 为false时,会等待这个任务执行完,返回true;若任务还没执行,取消任务后返回true,如任务执行完,返回false
boolean cancel(boolean mayInterruptIfRunning);
//判断任务是否被取消了,正常执行完不算被取消
boolean isCancelled();
//判断任务是否已经执行完成,任务取消或发生异常也算是完成,返回true
boolean isDone();
//获取任务返回结果,如果任务没有执行完成则等待完成将结果返回,如果获取的过程中发生异常就抛出异常,
//比如中断就会抛出InterruptedException异常等异常
V get() throws InterruptedException, ExecutionException;
//在规定的时间如果没有返回结果就会抛出TimeoutException异常
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
这个接口可以看成对任务多加一些功能,这些功能包括任务取消、任务是否取消了,任务是否执行完成了,任务执行完后返回的结果是什么。
现在就来看看FutureTask的构造方法:
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of 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;
}
}
有两个构造方法,其中传Runnable的最后通过适配器的方式装换成了Callable,这样的话,FutureTask一定就会持有Callable对象的应用,当线程调用到run()方法时,里面调用到Callable的call() 方法就可以了。执行流程可以看成是这样的Runnable(run())--->Callable(call())。由于FutureTask实现了Future接口,这样就可以达到对run()方法实现一个监视的功能,其实就是对线程的一个监视了。线程的执行状态是通过state来保存的
那就来看看state有哪些状态:
/** Possible state transitions:
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
*/
private volatile int state;
//初始状态,还没有执行的,这个是在构造方法中进行赋值的
private static final int NEW = 0;
//任务已经执行完成或者执行任务的时候发生异常,但是任务执行结果或者异常原因还没有
//保存到outcome字段(outcome字段用来保存任务执行结果,如果发生异常,则用来保存异常原因)的时候,
// 状态会从NEW变更到COMPLETING。但是这个状态会时间会比较短,属于中间状态
private static final int COMPLETING = 1;
//任务正常执行完的最终状态
private static final int NORMAL = 2;
//任务发生异常时的最终状态
private static final int EXCEPTIONAL = 3;
//任务取消时的状态,任务取消当并不中断,即只调用了cancel(false)方法
private static final int CANCELLED = 4;
//用户调用了cancel(true)方法取消任务,状态会从NEW转化为INTERRUPTING。这是一个中间状态。最终状态是INTERRUPTED
private static final int INTERRUPTING = 5;
//调用interrupt()中断任务,中断任务的最终状态
private static final int INTERRUPTED = 6;
现在就来看看线程开始执行的run()方法,FutureTask--->run():
public void run() {
//如果state不是NEW状态,说明任务已经执行过或是被取消了,就直接返回
if (state != NEW || !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();//执行任务,这里就说明真正的任务就是Callable中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);
}
}
可以看到这里对任务主要有三种处理,正常执行完、发生异常、或是中断,这三种处理正是对应上面state的几种状态,这里我们先来看下正常执行完的情况set():
protected void set(V v) {
if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
outcome = v;
U.putOrderedInt(this, STATE, NORMAL); // final state
finishCompletion();
}
}
这里就是对任务状态的一个转换,从COMPLETING转到NORMAL,并将运行结果保存到outcome,setException()的执行逻辑和这一样,最后就是执行finishCompletion():
private void finishCompletion() {
// assert state > COMPLETING;
//遍历等待的线程节点,被唤醒的线程会各自从awaitDone()方法中的LockSupport.park*()阻塞中返回
for (WaitNode q; (q = waiters) != null;) {
if (U.compareAndSwapObject(this, WAITERS, 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
}
你调用get()(后面会分析到)方法获取结果时,如果任务还没有执行完成时,那么就会处于等待状态,那么这个等待的线程就会加入到waiters中,如果你没有调用到get()方法,那么这个for循环就不会执行,说到这,你也许就明白了,就是唤醒在等待这个任务结果的线程并释放资源。
既然说到获取结果的get()方法,那就来看看这个方法:
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
任务还没执行完就会进入到awaitDone()这个方法中:
private int awaitDone(boolean timed, long nanos) throws InterruptedException {
long startTime = 0L; // Special value 0L means not yet parked
WaitNode q = null;
boolean queued = false;
for (;;) {
int s = state;
//任务完成、取消或是异常进入,会返回执行完后的状态
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
//肯能任务线程被阻塞了,当前线程出让执行权
else if (s == COMPLETING)
// We may have already promised (via isDone) that we are done
// so never return empty-handed or throw InterruptedException
Thread.yield();
//线程中断了,移除等待线程并抛出异常
else if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
//当前等待节点为null,则初始化新节点并关联到当前线程
else if (q == null) {
if (timed && nanos <= 0L)
return s;
q = new WaitNode();
}
//将当前节点添加到WAITERS,等待任务完成时被唤醒
else if (!queued)
queued = U.compareAndSwapObject(this, WAITERS,
q.next = waiters, q);
else if (timed) {
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;
}
// nanoTime may be slow; recheck before parking
if (state < COMPLETING)
//将当前线程挂起指定时间,时间到了没有被唤醒则会抛异常
LockSupport.parkNanos(this, parkNanos);
}
else
//挂起当前线程
LockSupport.park(this);
}
}
这是一个死循环,不过这里面有一个阻塞,会将当前线程挂起,当任务完成时会被唤醒,唤醒在finishCompletion()中完成,前面有分析到,唤醒后会将执行的状态返回state。
这样就进入到get()的report()方法中了:
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);
}
还记得前面的set()方法么,在那里将运行结果赋值给outcome,如果是正常运行完毕的,那就将结果返回,如果不是,那就将抛出相应的异常。到这里,FutureTak就分析的差不多了,如果有不懂的地方,可自行看看源码,它的其他方法还是比较好看懂的,如果有分析不对的地方还望指正,一起学习。