异步回调 :Future

本文详细解读了Java中的Future接口和其重要实现FutureTask,包括任务的异步获取、取消和查询,以及ScheduledFutureTask如何扩展为周期执行任务。重点介绍了FutureTask的状态转换和关键方法,如run(), set(), get()和cancel(),以及ScheduledFutureTask的简单实现和调度原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Future接口

Future 设计的初衷: 对将来的某个事件的结果进行建模
在这里插入图片描述
Future模式,可以让调用方立即返回,然后它自己会在后面慢慢处理,此时调用者拿到的仅仅是一个凭证,调用者可以先去处理其它任务,在真正需要用到调用结果的场合,再使用凭证去获取调用结果。这个凭证就是这里的Future

Future模式下的数据获取:

在这里插入图片描述
JDK提供了另一个接口——Callable,表示一个具有返回结果的任务:

public interface Callable<V> {
    V call() throws Exception;
}

Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

Future模式可以让调用方获取任务的一个凭证,以便将来拿着凭证去获取任务结果,凭证需要具有以下特点:

  1. 在将来某个时间点,可以通过凭证获取任务的结果;
  2. 可以支持取消。

FutureTask就是实现Future接口的凭证

Future接口有5个方法:

  1. boolean cancel(boolean mayInterruptIfRunning)
    尝试取消当前任务的执行。如果任务已经取消、已经完成或者其他原因不能取消,尝试将失败。如果任务还没有启动就调用了cancel(true),任务将永远不会被执行。如果任务已经启动,参数mayInterruptIfRunning将决定任务是否应该中断执行该任务的线程,以尝试中断该任务。 如果任务不能被取消,通常是因为它已经正常完成,此时返回false,否则返回true
  2. boolean isCancelled()如果任务在正常结束之前被被取消返回true
  3. boolean isDone()
    正常结束、异常或者被取消导致任务完成,将返回true
  4. V get() 这个方法会产生阻塞,会一直等到任务执行完毕才返回;
    等待任务结束,然后获取结果,如果任务在等待过程中被终端将抛出InterruptedException,如果任务被取消将抛出CancellationException,如果任务中执行过程中发生异常将抛出ExecutionException。
  5. 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

在这里插入图片描述

  1. FutureTask虽然支持任务的取消(cancel方法),但是只有当任务是初始化(NEW状态)时才有效,否则cancel方法直接返回false;
  2. 当执行任务时(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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值