Future以及FutureTask

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:能用来包装一个CallableRunnable对象,因为它实现了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吗?

参考文章FutureTask的使用方法及实现原理 - 掘金

四、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;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值