Future接口
Future 设计的初衷: 对将来的某个事件的结果进行建模
Future模式,可以让调用方立即返回,然后它自己会在后面慢慢处理,此时调用者拿到的仅仅是一个凭证,调用者可以先去处理其它任务,在真正需要用到调用结果的场合,再使用凭证去获取调用结果。这个凭证就是这里的Future。
Future模式下的数据获取:
JDK提供了另一个接口——Callable
,表示一个具有返回结果的任务:
public interface Callable<V> {
V call() throws Exception;
}
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
Future模式可以让调用方获取任务的一个凭证,以便将来拿着凭证去获取任务结果,凭证需要具有以下特点:
- 在将来某个时间点,可以通过凭证获取任务的结果;
- 可以支持取消。
FutureTask就是实现Future接口的凭证
Future接口有5个方法:
- boolean cancel(boolean mayInterruptIfRunning)
尝试取消当前任务的执行。如果任务已经取消、已经完成或者其他原因不能取消,尝试将失败。如果任务还没有启动就调用了cancel(true),任务将永远不会被执行。如果任务已经启动,参数mayInterruptIfRunning将决定任务是否应该中断执行该任务的线程,以尝试中断该任务。 如果任务不能被取消,通常是因为它已经正常完成,此时返回false,否则返回true - boolean isCancelled()如果任务在正常结束之前被被取消返回true
- boolean isDone()
正常结束、异常或者被取消导致任务完成,将返回true - V get() 这个方法会产生阻塞,会一直等到任务执行完毕才返回;
等待任务结束,然后获取结果,如果任务在等待过程中被终端将抛出InterruptedException,如果任务被取消将抛出CancellationException,如果任务中执行过程中发生异常将抛出ExecutionException。 - V get(long timeout, TimeUnit unit) 任务最多在给定时间内完成并返回结果,如果没有在给定时间内完成任务将抛出TimeoutException。
整个Future模式其实就三个核心组件:
- 真实任务/数据类(通常任务执行比较慢,或数据构造需要较长时间),即task任务类
- Future接口(调用方使用该凭证获取真实任务/数据的结果),即Future接口
- Future实现类(用于对真实任务/数据进行包装),即FutureTask实现类
FutureTask
在J.U.C提供的Future模式中,最重要的就是FutureTask
类
表示返回查询计算是否完成并且获取计算结果的对象。只有当计算完成时才能获取到计算结果,一旦计算完成,计算将不能被重启或者被取消,除非调用runAndReset方法
状态参数
NEW
: 0—表示是个新的任务或者还没被执行完的任务。这是初始状态。
COMPLETING
:1—任务已经执行完成或者执行任务的时候发生异常,但是任务执行结果或者异常原因还没有保存到outcome字段(outcome字段用来保存任务执行结果,如果发生异常,则用来保存异常原因)的时候,状态会从NEW变更到COMPLETING。但是这个状态会时间会比较短,属于中间状态。
NORMAL
:2—任务已经执行完成并且任务执行结果已经保存到outcome字段,状态会从COMPLETING转换到NORMAL。这是一个最终态。
EXCEPTIONAL
:3—任务执行发生异常并且异常原因已经保存到outcome字段中后,状态会从COMPLETING转换到EXCEPTIONAL。这是一个最终态。
CANCELLED
:4–任务还没开始执行或者已经开始执行但是还没有执行完成的时候,用户调用了cancel(false)方法取消任务且不中断任务执行线程,这个时候状态会从NEW转化为CANCELLED状态。后面将不会被执行 这是一个最终态。
INTERRUPTING
:5–当任务调用cancel(true)中断程序时,当前任务处于中断中,这是一个中间状态。中断只是线程的标记位–后面拿到中断标记位响应中断比如做退出操作,并不是将线程给中断了
INTERRUPTED
:6–调用interrupt()中断任务执行线程之后状态会从INTERRUPTING转换到INTERRUPTED。这是一个最终态。 有一点需要注意的是,所有值大于COMPLETING的状态都表示任务已经执行完成(任务正常执行完成,任务执行异常或者任务被取消)。
重要属性
Callable
callable:被提交的任务
Object outcome
:正常情况任务执行结束outcome保存结果或者任务异常时callable向上抛出异常,outcome保存异常结果
volatile Thread runner
:执行任务的线程的对象引用
volatile WaitNode waiters
:当有很多线程去get当前任务结果使用了stack栈的队列(头部插入头部获取)
waiters指向一个“无锁栈”,该栈保存着所有等待线程,我们知道当调用FutureTask的get方法时,如果任务没有完成,则调用线程会被阻塞,其实就是将线程包装成WaitNode
结点保存到waiters指向的栈中
long stateOffset
:state字段的内存偏移量
long runnerOffset
:runner字段的内存偏移量
long waitersOffset
:waiters字段的内存偏移量
后三个字段是配合Unsafe类做CAS操作使用的。
构造器
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;//程序员自己实现的业务类
this.state = NEW//当前状态初始化新建状态为0
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);//将程序员的runnable业务类通过适配器模式封装成了callable,通过get获取执行结果时,此结果可能为null或者为传进来的值
this.state = NEW;//当前状态初始化新建状态为0
}
内部状态转换
①. 任务正常执行并返回。 NEW -> COMPLETING -> NORMAL
②. 执行中出现异常。NEW -> COMPLETING -> EXCEPTIONAL
③. 任务执行过程中被取消,并且不响应中断。NEW -> CANCELLED
④.任务执行过程中被取消,并且响应中断。 NEW -> INTERRUPTING -> INTERRUPTED
- FutureTask虽然支持任务的取消(cancel方法),但是只有当任务是初始化(NEW状态)时才有效,否则cancel方法直接返回false;
- 当执行任务时(run方法),无论成功或异常,都会先过渡到COMPLETING状态,直到任务结果设置完成后,才会进入响应的终态。
重要方法解析
run()
//submit(runnable/callable)->newTaskFor(runnable)-->execute(task)-->pool
public void run() { //任务执行的入口
//当前任务被执行过或者被cancel过即非new的状态,直接不处理了
//条件成立cas失败:当前线程被其他线程抢占了
if (state != NEW || !RUNNER.compareAndSet(this, null, Thread.currentThread()))
return;
try {
//执行到这里当前任务一定是new的状态而且当前线程也抢占Task成功
Callable<V> c = callable; //当前程序员的业务引用:callable或者装饰后的runnable
if (c != null && state == NEW) {//防止空指针和防止外部线程取消掉当前任务
V result;//结果的引用
boolean ran;//ture表示代码块执行成功没有返回异常;false表示代码块执行失败抛出异常
try {
result = c.call();//程序员自己的实现的callable或者被装饰模式装饰过的runnable
ran = true;//c.call()没有抛出任何异常设置为ture
} catch (Throwable ex) {
result = null;
ran = false; //程序员自己的业务逻辑出现bug抛出任何异常设置为false
setException(ex);
}
if (ran)//c.call()正常执行结束
set(result);//设置result的结果到outcome
}
} finally {
runner = null;//任务执行结束,runner设置为null,表示当前没有线程在执行这个任务了
int s = state;
if (s >= INTERRUPTING)//读取状态,判断是否在执行的过程中,被中断了,如果被中断,处理中断
handlePossibleCancellationInterrupt(s);
}
}
set()
protected void set(V v) {
//使用CAS设置当前任务为完成中....
//外部线程等不及直接在set执行CAS之前直接将task取消了,此时会失败--概率比较小
if (STATE.compareAndSet(this, NEW, COMPLETING)) {
outcome = v;
STATE.setRelease(this, NORMAL); //将结果赋值给outcome之后,将当前任务状态设置为NORMAL即正常结束状态
finishCompletion();//唤醒被get()阻塞的线程
}
setException()
protected void setException(Throwable t) {
//使用CAS设置当前任务为完成中....
//外部线程等不及直接在set执行CAS之前直接将task取消了,此时会失败--概率比较小
if (STATE.compareAndSet(this, NEW, COMPLETING)) {
outcome = t;//引用的是callable向上层抛出来的异常
STATE.setRelease(this, EXCEPTIONAL) // 将结果赋值给outcome之后,将当前任务状态设置为NORMAL即正常结束状态
finishCompletion();//唤醒被get()阻塞的线程
}
}
get()
public V get() throws InterruptedException, ExecutionException {//可能会存在多个线程等待任务执行完成的结果,此方法可能会产生阻塞,会一直等到任务执行完毕才返回
int s = state;//获取当前任务状态
if (s <= COMPLETING)//条件成立:执行、正在执行、正在完成中、调用get的外部线程会被阻塞在此方法中
s = awaitDone(false, 0L);//返回task的状态,可能当前线程在方法里已经休眠了一段时间--阻塞过程--核心、重点
return report(s);
}
阻塞方法awaitDone()
private int awaitDone(boolean timed, long nanos)throws InterruptedException {
long startTime = 0L; // 0不带超时
WaitNode q = null;//引用当前线程封装成waitNode对象
boolean queued = false;//表示当前线程waitNode对象没有进入队列
for (;;) {//自循环操作
int s = state;//获取最新的状态
if (s > COMPLETING) {//表示任务已经执行结束,或者发生异常结束了,此时,调用get()的线程就不会阻塞
if (q != null)//条件成立:已经创建node了
q.thread = null;
return s;
}
else if (s == COMPLETING)//表示任务接近结束(正常/异常)状态,但是结果还没有保存到outcome对象里,当前线程让出cpu执行权,给其他线程先执行
Thread.yield();
else if (Thread.interrupted()) {//当前线程唤醒是被其他线程中断这种方式唤醒的Thread.interrupted()返回true会将中断标记位重置为false
removeWaiter(q);//等待的线程栈中移除这个等待节点node,然后抛出中断异常
throw new InterruptedException();//抛出中断异常
}
else if (q == null) {//第一次自循环当前线程还没有创建waitNode对象,此时为当前线程创建waitNode对象
if (timed && nanos <= 0L)
return s;
q = new WaitNode();
}
else if (!queued){//第二次自循环当前线程已经创建waitNode对象,但是还没有进入队列
// q.next = waiters//当前线程的node节点next指针指向原来队列的头部节点使waiters一直指向队列的头部
queued = WAITERS.weakCompareAndSet(this, q.next = waiters, q);
//CAS设置waiters引用指向当前线程节点node,如果成功则queued=ture;否则其他线程先入队列了则返回false则继续自循环直到成功为止
}
else if (timed) {//第三次自循环带超时操作--设置了超时时间,则判断是否达到超时时间,如果到达,则移除FutureTask的所有阻塞列队中的线程,并返回此时FutureTask的状态,如果未到达时间,则在剩下的时间内继续阻塞当前线程
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);//当前get操作的线程被阻塞,线程变成waiting状态,相当于休眠了,除非有其他线程唤醒或者将当前线程中断
}
}
awaitDone方法小结:执行顺序
a. 判断q == null,如果等待节点q为null,就创建waitNode等待节点,这个节点后面会被插入阻塞队列。
b. 判断queued有没有进入队列,没有将当前线程的node节点next指针指向原来队列的头部节点使waiters一直指向队列的头部然后使用CAS方法设置waiters引用指向当前线程节点node。通过WaitNode可以遍历整个阻塞队列。
c. 之后,判断timed,这是从get()传入的值,表示是否设置了超时时间。设置超时时间之后,调用get()的线程最多阻塞nanos,就会从阻塞状态醒过来。如果没有设置超时时间,就直接进入阻塞状态,等待被其他线程唤醒或者将当前线程中断
d. 读取FutureTask的最新state,如果s > COMPLETING,表示任务已经执行结束,或者发生异常结束了,此时,调用get()的线程就不会阻塞;如果s == COMPLETING,表示任务接近结束(正常/异常)状态,但是结果还没有保存到outcome对象里,当前线程让出执行权,给其他线程先执行。
e. 判断Thread.interrupted(),如果调用get()的线程被中断了,就从等待的线程栈(其实就是一个WaitNode节点队列或者说是栈)中移除这个等待节点,然后抛出中断异常。
报告结果
private V report(int s) throws ExecutionException {
Object x = outcome;//保存callable运行的结果或者callable抛出的异常
if (s == NORMAL)//属于正常结束返回callable运算结果
return (V)x;
if (s >= CANCELLED)//异常结束被中断状态或者取消状态直接抛出取消异常
throw new CancellationException();
throw new ExecutionException((Throwable)x);//callable接口执行有bug
}
report会根据任务的状态进行映射,如果任务是Normal状态,说明正常执行完成,则返回任务结果;如果任务被取消(CANCELLED或INTERRUPTED),则抛出CancellationException;其它情况则抛出ExecutionException。
唤醒被get()阻塞的线程方法
private void finishCompletion() {
// assert state > COMPLETING; q指向waiters链表的头结点
for (WaitNode q; (q = waiters) != null;) {
if (WAITERS.weakCompareAndSet(this, q, null)) {//使用CAS设置waiters线程为null--怕外部线程使用cancel()方法取消当前任务也会触发finishCompletion()
for (;;) {
Thread t = q.thread;//获取当前node节点封装为thread
if (t != null) {//当前节点不为null
q.thread = null;//有利于JVM垃圾回收
LockSupport.unpark(t);//唤醒当前节点对应的线程
}
WaitNode next = q.next;//拿到当前节点的下一个节点
if (next == null)//是最后一个节点说明都唤醒完成跳出循环
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null;//将callable设置为空,利于垃圾回收
}
取消任务
public boolean cancel(boolean mayInterruptIfRunning) {
//条件一 state==new--表示当前任务处于新建状态才能取消
//条件二 mayInterruptIfRunning 根据进行状态修改:成功执行下面逻辑,失败返回false取消失败
if (!(state == NEW && STATE.compareAndSet(this, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {//给当前消除发送中断信号
try {
Thread t = runner;//执行当前线程的引用 如果为null则当前任务在队列中但没有获取到
if (t != null)//runner引用的线程正在执行任务
t.interrupt();//给runner引用的线程发送中断信号,如果你的代码是响应中断的则进入中断逻辑;不是响应中断的则没有操作
} finally { // final state
STATE.setRelease(this, INTERRUPTED);//设置任务状态为中断完成
}
}
} finally {
finishCompletion();//唤醒所有被get()阻塞的线程
}
return true;
}
ScheduledFutureTask
ScheduledFutureTask是ScheduledThreadPoolExecutor这个线程池的默认调度任务类。
ScheduledFutureTask在普通FutureTask的基础上增加了周期执行/延迟执行的功能。通过下面的类图可以看到,它其实是通过继承FutureTask和Delayed接口来实现周期/延迟功能的
ScheduledFutureTask(Callable<V> callable, long ns) {
super(callable);
this.time = ns;
this.period = 0;
this.sequenceNumber = sequencer.getAndIncrement();
}
ScheduledFutureTask的源码非常简单,基本都是委托FutureTask来实现的
public void run() {
if (!canRunInCurrentRunState(this))// 能否运行任务
cancel(false);
else if (!isPeriodic()) // 非周期任务:调用FutureTask的run方法运行
super.run();
else if (super.runAndReset()) {// 周期任务:调用FutureTask的runAndReset方法运行
setNextRunTime();
reExecutePeriodic(outerTask);
}
}
FutureTask的runAndReset
方法与run方法的区别就是当任务正常执行完成后,不会设置任务的最终状态(即保持NEW状态),以便任务重复执行
protected boolean runAndReset() {
// 仅NEW状态的任务可以执行
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 = null;
s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
return ran && s == NEW;
}