Future介绍
在学习Future之前,需要先了解传统模式与future模式的区别,我觉得这个很重要。但是确实又不好说明,另起一篇文章。
Future模式介绍_Mr_moving-CSDN博客_future模式
future模式是一种并发设计模式,Future接口是JDK对该模式提供的一种实现,两者并无必然关系。
1. Future 表示异步的结果计算,Future 接口主要定义了5个方法:
1) boolean cancel(boolean mayInterruptIfRunning):试图取消对此任务的执行。如果任务已完成、或已取消,或者由于某些其他原因而无法取消,则此尝试将失败。当调用 cancel 时,如果调用成功,而此任务尚未启动,则此任务将永不运行。如果任务已经启动,则 mayInterruptIfRunning 参数确定是否应该以试图停止任务的方式来中断执行此任务的线程。此方法返回后,对 isDone() 的后续调用将始终返回 true。如果此方法返回 true,则对 isCancelled() 的后续调用将始终返回 true。
2)boolean isCancelled():如果在任务正常完成前将其取消,则返回 true。
3)boolean isDone():如果任务已完成,则返回 true。 可能由于正常终止、异常或取消而完成,在所有这些情况中,此方法都将返回 true。
4)V get() throws InterruptedException,ExecutionException:如有必要,等待计算完成,然后获取其结果。
5)V get(long timeout,TimeUnit unit) throws InterruptedException,ExecutionException,TimeoutException:尝试获取其结果,如有必要,最多等待为使计算完成所给定的时间timeout,如果超时仍未完成计算,抛出超时异常。
InterruptedException 线程中断异常
ExecutionException 任务执行异常,比如中途被取消
TimeoutException 超时异常
2. Future的类图结构
RunnableFuture:这个接口同时继承Future接口和Runnable接口,在成功执行run()方法后,可以通过Future访问执行结果。这个接口的实现类是FutureTask,一个可取消的异步任务,这个类提供了Future的基本实现,后面我们的demo也是用这个类实现,它实现了启动和取消一个任务,查询这个任务是否已完成,获取任务结果。任务的结果只能在任务已经完成的情况下获取。如果任务没有完成,get方法会阻塞,一旦任务完成,这个任务将不能被重启和取消,除非调用runAndReset方法。
FutureTask:能用来包装一个Callable或Runnable对象,因为它实现了RunnableFuture接口,而且它能被传递到Executor进行执行。
SchedualFuture:这个接口表示一个可以延时任务的结果。
其他如 CompleteFuture,ForkJoinTask 了解的不多,用的也比较少,暂时就不做记录。
3. FutureTask 源码分析
字段分析
private volatile int state;
/** 任务目标 */
private Callable<V> callable;
/** 任务结果,非volatile,受state读/写保护 */
private Object outcome;
/** 底层执行callable任务的线程,在run()方法中使用CAS操作赋值 */
private volatile Thread runner;
/** 阻塞在get方法上的线程节点,传动栈数据结构,后进先出 */
private volatile WaitNode waiters;
一、state 任务的状态值
使用volatile修饰,FutureTask中使用CAS操作更新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;
0==NEW==>初始创建时的状态
1==COMPLETING==>当任务执行完毕,FutureTask会将执行结果设置给outcome属性,在设置之前会将FutureTask的状态修改为COMPLETING。
2==NORAML==>当任务执行完毕,FutureTask会将执行结果设置给outcome属性,在设置之后会将FutureTask的状态修改为NORMAL。
3==EXCEPTIONAL==>当任务在执行的过程中抛了异常,FutureTask会将异常信息设置给outcome属性,在设置之前会将FutureTask的状态修改为COMPLETING,在设置之后将状态修改为EXCEPTIONAL。
4==CANCELLED==>当外部想要取消任务,而又不允许当任务正在执行的时候被取消时会将FutureTask的状态修改为CANCELLED。
5==INTERRUPTING==>当外部想要取消任务,同时允许当任务正在执行的时候被取消时,会先将FutureTask的状态设置为INTERRUPTING,然后设置执行任务的线程的中断标记位。
6==INTERRUPTED==>当外部想要取消任务,同时允许当任务正在执行的时候被取消时,会先将FutureTask的状态设置为INTERRUPTING,然后设置执行任务的线程的中断标记位,最后将Future的状态设置为INTERRUPTED。
其中0为初始状态,1和5为中间状态,其他为终止状态,在FutureTask中,state只会被set(), setException(),cancel()修改为终止状态。
综上,FutureTask的状态转换流转可能为:
1. 任务执行正常
NEW—>COMPLETING—>NORMAL
2. 任务执行异常
NEW—>COMPLETING—>EXCEPTIONAL
3. 任务未得到执行直接取消,或者任务正在执行过程中被取消,但是不允许中断cancel(false)
NEW—>CANCELLED
4. 任务正在执行过程中被取消,允许执行中进行中断cancel(true)
NEW—>INTERRUPTING—>INTERRUPTED
二、runner 正在执行此任务的线程
一个任务同时只能被一个线程执行,在run()方法中通过cas进行更新
三、outcome 任务运行的结果
任务结果的赋值
public void run() {
// 仅当任务状态为NEW,且无线程用于运行此任务时,更新runner并开始执行任务。
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();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
// 任务完成,但执行中出现异常,将异常信息传入赋值给outcome
setException(ex);
}
if (ran)
// 任务正常完成,将结果传入赋值给outcome
set(result);
}
} finally {
/**
* state被设置前必须保证runner非空,以阻止run()被并发调用。
* 进入finally块,则state已经被设置,任务执行完毕,此时可以安全的将runner置为null
*/
runner = null;
// runner置为null后,必须重新读取state以防止任务在执行中被取消,
int s = state;
// 有中断发生,以进行相应的逻辑操作
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
protected void setException(Throwable t) {
// 更新任务状态,此时为中间态COMPLETING
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
// 更新最终状态为EXCEPTIONAL
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
// 任务完成后,假设有线程阻塞在get方法上,调用此方法进行唤醒,放在它处分析
finishCompletion();
}
}
protected void set(V v) {
// 更新任务状态,此时为中间态COMPLETING
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
// 更新最终状态为NORMAL
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
任务结果通过get()方法获取
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
/**
* 阻塞等待任务完成返回
* 任务完成包含多种情形:
* 1. 正常完成,状态值为2NORMAL
* 2. 执行过程中出现异常,状态值为3EXCEPTIONAL
* 3. 任务取消,状态值为4CANCELLED或者6INTERRUPTED
* 假设允许中断取消,线程中断,awaitDone也会直接抛出中断异常InterruptedException
**/
s = awaitDone(false, 0L);
// 任务完成返回,根据状态值对结果outcome进行处理并返回
return report(s);
}
private V report(int s) throws ExecutionException {
Object x = outcome;
// 正常完成
if (s == NORMAL)
return (V)x;
// 取消
if (s >= CANCELLED)
throw new CancellationException();
// 通过上面的分析,这里的状态值应该就是为3EXCEPTIONAL了,outcome为异常对象,抛出
throw new ExecutionException((Throwable)x);
}
通过上面的分析,也了解到了状态值state的一些变化流程。其中有个问题,为什么要多此一举,先把状态改为COMPLETING然后又改为COMPLETED,不能直接改为COMPLETED吗?
四、waiters 等待获取结果的线程
waiters 维护了阻塞在get方法上的线程节点,使用传动栈数据结构,后进先出。Treiber栈是一个无锁数据结构,FutureTask中的waiters变量指向这个栈的栈顶。入栈操作只通过一步CAS操作实现,即修改栈顶指针waiters;出栈和在栈的中间执行删除操作通过特定的循环操作实现。
简单的单向链表,维护了节点的线程信息。
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
查构造器的调用发现,仅在awaitDown方法中调用,即等待任务完成的过程中。而awaitDown方法的调用栈也仅有两处:
我们再看awaitDown方法的说明 以及源码
/**
* 等待任务完成,或者由于中断或超时而放弃
* 任务完成或者超时则返回对应状态值,线程中断则抛异常
* 参数timed是否设置超时设置,nacos超时设定等待时间,若timed为false,则nacos为0
*/
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (; ; ) {
//判断当前线程是否已中断,该方法会清除线程状态,也就是说第一次调用返回true,
//并且没有再次被中断时,第二次调用将返回false
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
// 任务已完成或被取消
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
// 表示任务马上完成,不必进入等待队列
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
// 此时s只可能为NEW
else if (q == null)
q = new WaitNode();
else if (!queued)
// 使用CAS操作加入当前等待节点q,通过将q设为新的栈顶元素,即waiters,
// 同时修改q.next指针指向上一次的waiters。这里使用自旋操作来保证操作一定成功
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
LockSupport.park(this);
}
}
future状态转换图
五、任务取消
对于cancel方法,着重再分析一下源码
//有一个入参,需要说明task是否是可中断的
public boolean cancel(boolean mayInterruptIfRunning) {
// 任务状态为new,则根据入参true设置为中间态INTERRUPTING或false设置为终止态CANCELLED
// 如果上述操作无法完成,说明任务取消失败,返回false
if (!(state == NEW &&
UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
可以看到cancel方法主要做了两件事,1. 修改自身状态值 2. 中断任务执行线程(参数为true时)
取消操作只是改变了任务对象的状态并可能会中断执行线程(这里的中断只是给线程打上一个中断标记,关于线程中断的概念可查阅相关文章)。如果任务的逻辑代码没有响应中断,则会一直异步执行直到完成,只是最终的执行结果不会被通过get方法返回,计算资源的开销仍然是存在的。
对于尚未启动的任务,调用cancle后将永远无法被执行。
六、runAndReset 运行并重置任务
FutureTask一旦执行结束,任务就不能被重启或取消(除非使用runAndReset执行计算)
/**
* 执行计算而不设置其结果,所以无法通过get获取到任务结果,同时保持此任务为初始状态,
* 如果计算遇到异常或被取消,则无法这样再次重启任务
* 这是专为与 本质上执行多次的任务 一起使用而设计的
*/
protected boolean runAndReset() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return false;
boolean ran = false;
int s = state;
try {
Callable<V> c = callable;
if (c != null && s == NEW) {
try {
c.call(); // don't set result
ran = true;
} catch (Throwable ex) {
setException(ex);
}
}
} 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
s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
return ran && s == NEW;
}