FutureTask源码解析

264 阅读6分钟

小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

FutureTask是一个未来任务,可以获取返回结果,通过get方法来完成,但get方法会阻塞直到任务执行完返回结果,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。

01

继承结构

图片

FutureTask实现了RunnableFuture,而RunnableFuture又继承了Future和Runnable,Future提供了返回结果的get方法,这里可能会有一个疑问,为什么submit方法返回的是Future而不是FutureTask。这是因为只想对外部调用者暴露get方法,而不想暴露FutureTask的run方法的能力。

02

 属性解析

// 表示task状态
    private volatile int state;
    // 当前任务尚未执行
    private static final int NEW          = 0;
    // 当前任务正在结束,一种临界状态
    private static final int COMPLETING   = 1;
    // 当前任务执行结束
    private static final int NORMAL       = 2;
    //  当前任务执行过程发送了异常,内部封装的callable.run()向上抛出了异常
    private static final int EXCEPTIONAL  = 3;
    // 当前任务被取消
    private static final int CANCELLED    = 4;
    // 当前任务中断中
    private static final int INTERRUPTING = 5;

    // 当前任务已中断
    private static final int INTERRUPTED  = 6;
    // submit(runnable/callable)runnable 使用 装饰者模式伪装成Callable
    private Callable<V> callable;
  // 正常情况下,任务正常执行结束,outcome保存执行结果,callable返回值
    // 非正常情况下:callable向上抛出异常,outcome保存异常
    private Object outcome; // non-volatile, protected by state reads/writes
    // 当前任务被线程执行期间,保存当前执行任务的线程对象引用
    private volatile Thread runner;
    // 因为会有很多线程去get当前线程结构
    // 所以这里使用了一种数据结构 stack 头插 头取 的一个队列
    private volatile WaitNode waiters;

FutureTask整个运行过程都需要通过当前任务运行状态来控制。

03

 run方法

当通过submit提交任务时,最后执行的是execute方法,我们知道execute()方法最后调用的是task的run()方法,上面我们传进去的任务,最后被包装成了FutureTask,也就是说execute()方法最后会调用到FutureTask的run()方法。

   //  线程任务执行入口
    public void run() {
        // cas失败,当前任务被其他线程抢占了
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        // 当前task一定是NEW状态,而且当前线程也抢占TASK成功
        try {
    // callable 就是程序员自己封装逻辑的callable 或者 装饰后的runnable
            Callable<V> c = callable;
         // 条件一:c!=null 防止空指针
         // state==NEW 防止外部线程cancel掉当前任务
            if (c != null && state == NEW) {
                // 结果引用
                V result;
                // true callable代码块执行成功
                // false 代码块执行失败,抛出异常
                boolean ran;
                try {
                  //调用程序员自己实现的callable或者装饰后的runnable
                    result = c.call();
                    //call方法没有抛出任何异常,ran会设置为true
                    ran = true;
                } catch (Throwable ex) {
                    //进入catch说明程序员自己写的逻辑块有bug了
                    result = null;
                    ran = false;
                    //异常设置到outcome
                    setException(ex);
                }
                // call方法正常执行结束
                if (ran)
                    // set就是设置结果到outcome
                    set(result);
            }
        } 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
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

  1. 首先判断当前任务的运行状态,如果当前任务不是初始化状态或者修改为当前线程来运行任务失败,就直接返回

  2. 如果当前任务是初始化状态并且callable不为空就调用callable的call方法运行当前任务

  3. 将返回的结果值设置到outcome中,并唤醒waiterNode中所有等待的线程

  4. 更新当前任务状态

04

 set方法

   protected void set(V v) {
        // 使用CAS方式设置当前任务状态为完成中
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {

            outcome = v;
            //将结果赋值给outcome之后,马上会将当前任务状态修改为NORMAL 正常结束状态
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            // 唤醒WaiterNode所有等待的线程
            finishCompletion();
        }
    }

set方法主要做了三件事

  1. 实时更新当前任务执行状态

  2. 将结果值赋值给outcome

  3. 调用finishCompletion方法唤醒所有调用get方法而阻塞的线程

05

 finishCompletion方法

 private void finishCompletion() {
        // 循环waitNode结点
        for (WaitNode q; (q = waiters) != null;) {

            // 使用cas设置 waiters为null,是因为怕外部线程使用 cancel取消当前任务
            // 也会触发finishCompletion方法
            if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
                //
                for (;;) {
                    Thread t = q.thread;
                    if (t != null) {
                        q.thread = null;
                        //唤醒当前节点对应线程
                        LockSupport.unpark(t);
                    }
                    // next 当前节点下一个结点
                    WaitNode next = q.next;
                    if (next == null)
                        break;
                    q.next = null; // unlink to help gc
                    q = next;
                }
                break;
            }
        }

        done();

        callable = null;        // to reduce footprint
    }

finishCompletion方法也很简单,就是循环遍历waitNode的每个节点,将每个节点进行唤醒。

06

get方法

   public V get() throws InterruptedException, ExecutionException {
       //获取当前任务状态
        int s = state;
        // 表示 未执行,正在执行,正完成
        if (s <= COMPLETING)
         //  将线程加入WaitNode,进入队列等待
            s = awaitDone(false, 0L);
        // 返回执行结果
        return report(s);
    }

判断当前任务状态,如果还没完成就调用awaitDone方法使当前线程加入阻塞队列进行等待,最后通过report方法返回执行结果。

07

awaitDone方法

  private int awaitDone(boolean timed, long nanos)
            throws InterruptedException {
        // 假设为0   不带超时
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        // 引用当前线程 封装成 WaitNode对象
        WaitNode q = null;
        boolean queued = false;
        for (;;) {
            // 当前线程唤醒是被其他线程使用中断这种方式唤醒的
            // 返回true 后会将Thread的中断标记重置回false
            if (Thread.interrupted()) {
                // 将当前线程node出队
                removeWaiter(q);
                // get方法抛出中断异常
                throw new InterruptedException();
            }
            // 假设当前线程是被其他线程使用unpark唤醒的话,会正常自旋,
            // 走下面逻辑
            // 获取当前任务最新状态
            int s = state;
           4. // 说明当前任务已经有结果了
            if (s > COMPLETING) {
                //不等于空说明已经为当前线程创建过node了,此时需要将
                // node。thread=null   helpgc
                if (q != null)
                    q.thread = null;
                //直接返回当前状态
                return s;
            }
            // 表示当前任务接近完成
            else if (s == COMPLETING) // cannot time out yet
                // 让当前线程再释放CPU,进行下一次抢占CPU
                Thread.yield();
                // 条件成立第一次自旋,当前线程还未创建WaitNode 对象,
            1.    // 此时为当前线程创建WaitNode对象
            else if (q == null)
                q = new WaitNode();
                // 第二次自旋,当前线程已经创建WaitNode对象了,
             2.   // 但是node对象还未入队
            else if (!queued)
                //当前线程node节点next指向原队列的头节点,waiters一直指向队列的头
                // cas方式设置waiters引用指向线程node
                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);
            }
            //当前get操作的线程就会被park,线程状态变为waiting状态,想当与休眠
            3. // 除非有其他线程将你唤醒,或者将当前线程中断
            else
                LockSupport.park(this);
        }
    }

这里我们假设调用get()时任务还未执行,也就是其状态为NEW,我们试着按上面标示的1、2、3、4走一遍逻辑:

  1. 第一次循环,状态为NEW,直接到1处,初始化队列并把调用者线程封装在WaitNode中;

  2. 第二次循环,状态为NEW,队列不为空,到2处,让包含调用者线程的WaitNode入队;

  3. 第三次循环,状态为NEW,队列不为空,且已入队,到3处,阻塞调用者线程;

  4. 假设过了一会任务执行完毕了,根据run()方法的分析最后会unpark调用者线程,也就是3处会被唤醒;

  5. 第四次循环,状态肯定大于COMPLETING了,退出循环并返回;

总结

  1. 未来任务是通过把普通任务包装成FutureTask来实现的。

  2. 通过FutureTask不仅能够获取任务执行的结果,还有感知到任务执行的异常,甚至还可以取消任务;

  3. AbstractExecutorService中定义了很多模板方法,这是一种很重要的设计模式;

  4. FutureTask其实就是典型的异常调用的实现方式。