Future接口
概述
Future接口代表异步计算的结果,它提供了检查计算是否完成,取消计算执行,等待计算完成并检索计算结果的方法。
如果你出于可取消计算执行的目的使用Future,但又不想返回结果,可以使用Future<?>数据类型,并使用null作为底层任务的返回结果。
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;
}
- cancel():cancel()方法用来取消异步任务的执行。如果异步任务已经完成或者已经被取消,或者由于某些原因不能取消,则会返回false。如果任务还没有被执行,则会返回true并且异步任务不会被执行。如果任务已经开始执行了但是还没有执行完成,若mayInterruptIfRunning为true,则会立即中断执行任务的线程并返回true,若mayInterruptIfRunning为false,则会返回true且不会中断任务执行线程。
- isCanceled():判断任务是否被取消,如果任务在结束(正常执行结束或者执行异常结束)前被取消则返回true,否则返回false。
- isDone():判断任务是否已经完成,如果完成则返回true,否则返回false。需要注意的是:任务执行过程中发生异常、任务被取消也属于任务已完成,也会返回true。
- get():获取任务执行结果,如果任务还没完成则会阻塞等待直到任务执行完成。如果任务被取消则会抛出CancellationException异常,如果任务执行过程发生异常则会抛出ExecutionException异常,如果阻塞等待过程中被中断则会抛出InterruptedException异常。
- get(long timeout,Timeunit unit):带超时时间的get()版本,如果阻塞等待过程中超时则会抛出TimeoutException异常。
Future使用例子如下:
interface ArchiveSearcher { String search(String target); }
class App {
ExecutorService executor = ...
ArchiveSearcher searcher = ...
void showSearch(final String target)
throws InterruptedException {
Future<String> future
= executor.submit(new Callable<String>() {
public String call() {
return searcher.search(target);
}});
displayOtherThings(); // do other things while searching
try {
displayText(future.get()); // use future
} catch (ExecutionException ex) { cleanup(); return; }
}
}
FutureTask类同时实现了Future接口和Runnable接口,所以FutureTask既能作为Future用来得到Callable的计算结果,也能作为一个Runnable被Executor执行,例如上述submit方法可以替换为:
FutureTask<String> future =
new FutureTask<String>(new Callable<String>() {
public String call() {
return searcher.search(target);
}});
executor.execute(future);
Callable接口
实现callable接口的task可以返回一个结果或者抛出一个异常。callable接口和Runnable接口是相似的,实现接口的task都用于被另外的线程执行。但是,Runnable接口不会返回一个结果,或者抛出异常。
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Callable接口是一个泛型接口,泛型V是call方法返回的类型。
FutureTask
概述
FutureTask是一个可取消的异步计算。因为这个类同时实现了Future接口和Runnable接口,所以它在除了具有Future接口的所有方法外,作为Future用来得到Callable的计算结果,还具有Runnable接口的run方法,可以作为一个Runnable被Executor执行。
jdk1.8的FutureTask实现不再依赖AQS实现,同步控制主要通过一个state字段实现,通过使用CAS更新state字段来追踪异步计算是否完成。
state字段
state字段用于保存异步任务的执行状态,它一共有以下7种状态:
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;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
- NEW:表示任务的初始状态。
- COMPLETING:任务已经执行完成或者执行任务的时候发生异常,但是任务执行结果或者异常原因还没有保存到outcome字段(outcome字段用来保存任务执行结果,如果发生异常,则用来保存异常原因)的时候,状态会从NEW变更到COMPLETING。但是这个状态会时间会比较短,属于中间状态。
- NORMAL:任务已经执行完成并且任务执行结果已经保存到outcome字段,状态会从COMPLETING转换到NORMAL。这是一个最终态。
- EXCEPTIONAL:任务执行发生异常并且异常原因已经保存到outcome字段中后,状态会从COMPLETING转换EXCEPTIONAL。这是一个最终态。
- CANCELLED:任务还没开始执行或者已经开始执行但是还没有执行完成的时候,用户调用了cancel(false)方法取消任务且不中断任务执行线程,这个时候状态会从NEW转化为CANCELLED状态。这是一个最终态。
- INTERRUPTING: 任务还没开始执行或者已经执行但是还没有执行完成的时候,用户调用了cancel(true)方法取消任务并且要中断任务执行线程但是还没有中断任务执行线程之前,状态会从NEW转化为INTERRUPTING。这是一个中间状态。
- INTERRUPTED:调用interrupt()中断任务执行线程之后状态会从INTERRUPTING转换到INTERRUPTED。这是一个最终态。
各个状态之间的可能转换关系如下:
- NEW -> COMPLETING -> NORMAL
- NEW -> COMPLETING -> EXCEPTIONAL
- NEW -> CANCELLED
- NEW -> INTERRUPTING -> INTERRUPTED
只能通过set、setException、cancel方法改变上述状态。
state大于COMPLETING都表示任务已经执行完成(任务正常执行完成,任务执行异常或者任务被取消)。
WaitNode
FutureTask的成员变量除了state字段外,还有以下成员变量:
/** The underlying callable; nulled out after running */
private Callable<V> callable;
/** The result to return or exception to throw from get() */
private Object outcome; // non-volatile, protected by state reads/writes
/** The thread running the callable; CASed during run() */
private volatile Thread runner;
/** Treiber stack of waiting threads */
private volatile WaitNode waiters;
其中,waiters字段是一个链表的头节点,该链表用于保存调用get方法阻塞等待异步任务完成的线程。WaitNode定义如下:
在创建一个waitNode节点时,会自动将当前线程封装成节点。
/**
* Simple linked list nodes to record waiting threads in a Treiber
* stack. See other classes such as Phaser and SynchronousQueue
* for more detailed explanation.
*/
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
outcome字段
outcome字段体现了FutureTask之所以能让当前线程获取异步任务的执行结果的实现机制。
run方法确实不能返回结果,那就设计callable接口,让它的call方法称为run方法内的子逻辑。
Runnable接口 Callable接口
Runnable实现类 callable实现类
Runnable实现类关联了callable实现类,具有它的成员变量;
run方法内部调用了他的call方法;
outcome字段不是线程安全的,可以被多个线程访问。正因如此,它成为任务执行线程和任务发起线程之间的通信桥梁。
任务执行线程将call方法的返回值设置给outcome字段。然后任务发起线程可以在state字段满足条件(> COMPLETING)后,访问outcome字段。
outcome字段不是volatile的,但在set方法和setException方法中,通过在设置outcome字段值的前后对state字段进行cas write,让outcome字段也具有volatile read/write语义。
构造函数
构造函数主要完成2件事情:
- 传入一个callable接口的实现,保存到callable字段作为底层调用;
- 设置当前任务的初始状态为NEW;
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
run方法
run方法的实现如下:
- 判断当前任务的state是否等于NEW,如果不为NEW则说明任务或者已经执行过,或者已经被取消,直接返回。
- 如果状态为NEW则接着会通过unsafe类把任务执行线程引用CAS的保存在runner字段中,如果保存失败,则直接返回。
- 调用callable字段的call方法执行任务。
- 如果任务执行发生异常,则调用setException()方法保存异常信息。
- 如果任务成功执行则调用set()方法设置执行结果。
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call(); //调用call方法执行任务
ran = true;
} catch (Throwable ex) {
result = null;
ran = false; //发生异常,设置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);
}
}
setException方法
setException方法在任务执行发生异常时被run方法调用,它的实现如下:
- 用CAS把当前的状态从NEW变更为COMPLETING状态。
- 把异常原因保存在outcome字段中,outcome字段用来保存任务执行结果或者异常原因。
- CAS的把当前任务状态从COMPLETING变更为EXCEPTIONAL。
- 调用finishCompletion()唤醒waiters链表中阻塞等待的线程。
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
set方法
set方法在任务执行成功时被run方法调用,实现如下:
- 用CAS把当前的状态从NEW变更为COMPLETING状态。
- 把任务执行结果保存在outcome字段中。
- CAS的把当前任务状态从COMPLETING变更为NORMAL。
- 调用finishCompletion()唤醒waiters链表中阻塞等待的线程。
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
get方法
get方法获取任务执行结果,如果任务未完成会阻塞等待直到任务完成。实现如下:
- 判断任务当前的state <= COMPLETING是否成立,如果成立,表明任务还没有结束(这里的结束包括任务正常执行完毕,任务执行异常,任务被取消),则会调用awaitDone()进行阻塞等待。
- 如果不成立表明任务已经结束,调用report()返回结果,根据state字段的最终态决定返回正常执行结果还是抛出相应异常。
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
awaitDone方法
awaitDone方法会判断任务的当前状态,将线程封装成节点,使用头插入法通过cas插入waitors链表,并进行阻塞等待。该方法在死循环中分别完成上述步骤。调用死循环的目的,是因为park方法总是需要在while循环中调用,以防虚假唤醒。
在这里,线程被唤醒后,只有线程中断状态为true,或者state字段大于completing,或者达到指定等待时间,才能从循环退出,否则继续调用park方法。
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
// 计算等待截止时间
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
// 1. 判断阻塞线程是否被中断,如果被中断则在等待队
// 列中删除该节点并抛出InterruptedException异常
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
// 2. 获取当前状态,如果状态大于COMPLETING
// 说明任务已经结束(要么正常结束,要么异常结束,要么被取消)
// 则把thread显示置空,并返回结果
int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
// 3. 如果状态处于中间状态COMPLETING
// 表示任务已经结束但是任务执行线程还没来得及给outcome赋值
// 这个时候让出执行权让其他线程优先执行
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
// 4. 如果等待节点为空,则构造一个等待节点
else if (q == null)
q = new WaitNode();
// 5. 如果还没有入队列,则把当前节点加入waiters首节点并替换原来waiters
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {
// 如果需要等待特定时间,则先计算要等待的时间
// 如果已经超时,则删除对应节点并返回对应的状态
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
// 6. 阻塞等待特定时间
LockSupport.parkNanos(this, nanos);
}
else
// 6. 阻塞等待直到被其他线程唤醒
LockSupport.park(this);
}
}
cancel方法
该方法会判断任务的当前状态,当状态为new时表示任务尚未开始执行,可以取消任务,否则表示任务执行完成或者即将执行完成,不能取消,返回false结束。
该方法实现了任务状态state的以下转换:
参数mayInterruptIfRunning为true:NEW -> INTERRUPTING -> INTERRUPTED
参数mayInterruptIfRunning为false:NEW -> CANCELLED
最后,调用finishCompletion()唤醒waiters链表中阻塞等待的线程。
public boolean cancel(boolean mayInterruptIfRunning) {
// 1. 如果任务已经结束,则直接返回false
if (state != NEW)
return false;
// 2. 如果需要中断任务执行线程
if (mayInterruptIfRunning) {
// 2.1. 把任务状态从NEW转化到INTERRUPTING
if (!UNSAFE.compareAndSwapInt(this, stateOffset, NEW, INTERRUPTING))
return false;
Thread t = runner;
// 2.2. 中断任务执行线程
if (t != null)
t.interrupt();
// 2.3. 修改状态为INTERRUPTED
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED); // final state
}
// 3. 如果不需要中断任务执行线程,则直接把状态从NEW转化为CANCELLED
else if (!UNSAFE.compareAndSwapInt(this, stateOffset, NEW, CANCELLED))
return false;
// 4. 唤醒waitors链表中阻塞等待的线程
finishCompletion();
return true;
}
finishCompletion方法
finishCompletion方法唤醒waiters链表中阻塞等待的线程,并将之从waitors链表删除。
/**
* Removes and signals all waiting threads, invokes done(), and
* nulls out callable.
*/
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
if (UNSAFE.compareAndSwapObject(this, waitersOffset, 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
}
report方法
该方法被get方法在被唤醒退出循环后调用,它将根据state字段的最终态决定返回正常执行结果还是抛出相应异常。
/**
* Returns result or throws exception for completed task.
*
* @param s completed state value
*/
@SuppressWarnings("unchecked")
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);
}