为了账号安全,请及时绑定邮箱和手机立即绑定

[Java源码][并发J.U.C]---解析FutureTask

标签:
Java

前言

Future接口和实现Future接口的FutureTask类,代表异步计算的结果. 简单点说就是实现有返回结果的task, 实现Runnable接口的线程没有提供获得线程返回的结果, 而FutureTask实现了异步获得计算结果的一种方式, 也就是说可以先让一个线程去执行该task后自己去干其他的事情,等到一段时间后可以来获取该task的执行结果.

本文源码: 本文源码地址

例子

先使用一个例子简单看看FutureTask的使用.

package com.futuretask;import java.util.concurrent.Callable;public class FutureTaskTest02 {    public static void main(String[] args) throws Exception {
        FutureTask<String> futureTask = new FutureTask< >(new Callable<String>() {            @Override
            public String call() throws Exception {
                System.out.println(Thread.currentThread().getName() + " starts to run.");
                Thread.sleep(5000);
                System.out.println(Thread.currentThread().getName() + " wakes up.");                return "futurecall";
            }
        });

        Thread thread = new Thread(futureTask);

        thread.start();

        System.out.println(Thread.currentThread().getName() + " finished to start thread.");

        System.out.println(Thread.currentThread().getName() + "->" + futureTask.get());
    }
}

由代码中可以看到初始化一个Thread时, 传入一个FutureTask对象, 正常创建一个线程, 要传入一个Runnable对象, 其实FutureTaskRunnable的一个子类. 所以就好理解了, 另外还注意的是FutureTask对象传入了一个Callable实例, 暂时可以理解call方法为Runnable里面的run方法,是线程要执行的实体. 接着启动线程后在主线程中可以获得线程
FutureTask的结果.

输出结果如下: 可以看到FutureTask中可以得到线程执行结束后得到的结果.

main finished to start thread.
Thread-0 starts to run.
Thread-0 wakes up.
main->futurecall

类结构

webp

futureTask.png

可以看到FutureTask实现了RunnableFuture接口, 然而RunnableFuture接口继承了Runnable接口和Future接口. 同时类FutureTask中使用了Callable对象, Callable接口定义了call由用户实现并且注入到FutureTask中.

由此可以猜测上例中thread中真正调用的是FutureTaskrun方法, 而run方法中实际调用了Callablecall方法并返回值, 关于取消获取返回值之类的方法都是FutureTask定义了一些逻辑来实现了Future的所有接口方法.

源码

接下来将分析一个FutureTask类.

属性

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;    /** 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;

之前有说FutureTask是设计了一些逻辑来实现Future接口中的方法. 这些逻辑的基本线就是基于state, state是表示当前线程执行该任务的一些状态值. 状态值就是代码中对应的那些值, 他们的状态值转换只有下面这四种可能性.

NEW -> COMPLETING -> NORMALNEW -> COMPLETING -> EXCEPTIONALNEW -> CANCELLEDNEW -> INTERRUPTING -> INTERRUPTED

接下来基于这四种可能性, 我们通过源码和例子共同来测试和查看, 弄明白每种可能性执行的代码逻辑. 最后加以总结.

构造方法

    public FutureTask(Callable<V> callable) {        if (callable == null)            throw new NullPointerException();        this.callable = callable;        this.state = NEW;       // ensure visibility of callable
    }    public FutureTask(Runnable runnable, V result) {        this.callable = Executors.callable(runnable, result);        this.state = NEW;       // ensure visibility of callable
    }

这个就不多说了, 可以看到初始状态为NEW.

run 方法

接着看要执行的run方法.

    /**
     *  最终运行的方法
     */
    public void run() {        // 如果状态值不为NEW 表示已经有线程运行过该task了 因此返回
        // 如果状态值为NEW 则设置RUNNER为当前线程 如果设置不成功也返回
        if (state != NEW ||                                                                 // 1
                !UNSAFE.compareAndSwapObject(this, runnerOffset,                        null, Thread.currentThread()))            return;        // 进入到这里 表明执行该task的是当前线程已经被设置到RUNNER变量中并且状态值state为NEW
        try {
            Callable<V> c = callable;            if (c != null && state == NEW) {                                                // 2
                /**
                 * result 接收callable的返回值
                 * ran    表示callable方法是否正确执行完成
                 */
                V result;
                boolean ran;                try {                    // 调用callable的方法call 并把结果放到result中
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {                                                    // 3
                    // call()方法出现异常执行的操作
                    result = null;
                    ran = false;
                    setException(ex);
                }                // call()正确执行完执行的操作
                if (ran)                                                                    // 4
                    set(result);
            }
        } finally {                                                                         
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            /**
             * 设置执行线程为null 不需要用CAS是因为前面的CAS会挡住其他的线程进入
             */
            runner = null;            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            /**
             *  如果s >= INTERRUPTING 则调用handlePossibleCancellationInterrupt(s)方法
             *  那什么时候会 s >= INTERRUPTING呢?
             *      -> 调用cancel(true)并且没有在setException(ex)和set(result)发生前
             */
            int s = state;            if (s >= INTERRUPTING)                                                          // 5              
                handlePossibleCancellationInterrupt(s);
        }
    }

按代码中顺序:
1. 如果状态值不为NEW, 表明有线程已经在执行这个run方法, 因此直接返回. 如果状态值为NEW则把当前线程设置到runner变量中,由于是多线程操作,为了保持线程间可见性, runner变量是volatile 并且用CAS设置.
2. 进入到第2步, 为什么需要进行判断if (c != null && state == NEW)呢? 这是因为如果刚刚结束完第1步正在进入第2步的过程中,别的线程启动了cancel(false/true)方法(该方法会在后面分析,此时只需要知道这个即可), 都会导致state不为NEW的. 接着就开始执行callablecall方法, 用result接受返回值, 用ran表示call方法是否完整顺利的执行.
3. 进入到第3步, 表明call方法出了异常没有正常顺利的执行完, 此时设置resultnull,ranfalse, 并且通过setException(ex)进入到异常结束状态. 看如下代码:

    protected void setException(Throwable t) {        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = t;
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }

可以看到会把异常对象传给outcome, 并且设置状态. 由此可见状态值是
NEW -> COMPLETING -> EXCEPTIONAL 并且 COMPLETING 是一个中间状态. finishCompletion()是做一些收尾工作,会放到后面分析.

4. 如果call方法正常顺利执行完, 则会调用set(result)设置正常结束状态. 看如下代码段:

protected void set(V v) {        //System.out.println(" set after state:" + state);
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;            //System.out.println(" set before state:" + state);
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }

可以看到outcome是保存最终的结果包括返回值或者异常对象. 由此可见状态值是NEW -> COMPLETING -> NORMAL 并且 COMPLETING 是一个中间状态. finishCompletion()会放到后面分析.

5. 这个是finally执行片段, 这段代码会把runner设置为null, 这里不需要用CAS是因为通过上面的片段,只有放到runner的那个线程才可以执行到try...finally...的片段. 上面也有提到过如果在第1步和第2步之间发生了cancel方法. 为了可以更加清晰的明白此代码段,看一下cancel方法的代码及其作用:



作者:nicktming
链接:https://www.jianshu.com/p/92d01d21ce0e


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消