FutureTask源码

255 阅读9分钟

前言

大家好,我是努力更文的小白。今天我们一起来看看FutureTask源码,说实话,看源码是一个非常枯燥的事情~

成员属性

属性如下:

//表示当前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;

/** The underlying callable; nulled out after running */
//线程池submit(runnable/callable)   runnable 使用 装饰者模式 伪装成 Callable了。
private Callable<V> callable;
/** The result to return or exception to throw from get() */
//正常情况下:任务正常执行结束,outcome保存执行结果。 callable 返回值。
//非正常情况:callable向上抛出异常,outcome保存异常
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 */
//因为会有很多线程去get当前任务的结果,所以 这里使用了一种数据结构 stack 头插 头取 的一个队列。
//当前获取当前任务的结果会将线程封装成WaitNode结构,如果获取不到会阻塞
private volatile WaitNode waiters;
  • state:表示当前task任务状态。
  • outcome:正常情况下:任务正常执行结束,outcome保存执行结果。 callable 返回值;非正常情况callable向上抛出异常,outcome保存异常。
  • runner:当前任务被线程执行期间,保存当前执行任务的线程对象引用。
  • waiters:因为会有很多线程去get当前任务的结果,所以 这里使用了一种数据结构stack头插 头取 的一个队列,当前获取当前任务的结果会将线程封装成WaitNode结构,如果获取不到会阻塞。

WaitNode结构如下:

static final class WaitNode {
    volatile Thread thread;//当前线程
    volatile WaitNode next;//指向下一个WaitNode
    WaitNode() { thread = Thread.currentThread(); }
}

静态代码块

静态代码块主要是通过unsafe类获取对应属性的偏移地址,方便后面的核心方法通过cas方式操作属性。静态代码块源码如下:

private static final sun.misc.Unsafe UNSAFE;
//state属性的偏移地址
private static final long stateOffset;
//runner属性的偏移地址
private static final long runnerOffset;
//waiters属性的偏移地址
private static final long waitersOffset;
static {
    try {
        UNSAFE = sun.misc.Unsafe.getUnsafe();
        Class<?> k = FutureTask.class;
        stateOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("state"));
        runnerOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("runner"));
        waitersOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("waiters"));
    } catch (Exception e) {
        throw new Error(e);
    }
}

构造方法

构造方法如下:

public FutureTask(Callable<V> callable) {
    if (callable == null)
        throw new NullPointerException();
    //callable就是程序员自己实现的业务类
    this.callable = callable;
    //设置当前任务状态为 NEW
    this.state = NEW;       // ensure visibility of callable
}

public FutureTask(Runnable runnable, V result) {
    //使用装饰者模式将runnable转换为了 callable接口,外部线程 通过get获取
    //当前任务执行结果时,结果可能为 null 也可能为 传进来的值。
    this.callable = Executors.callable(runnable, result);
    this.state = NEW;       // ensure visibility of callable
}

run方法

run方法为任务执行的入口,源码如下:

//submit(runnable/callable) -> newTaskFor(runnable) -> execute(task)   -> pool
//任务执行入口
public void run() {
    //条件一:state != NEW 条件成立,说明当前task已经被执行过了 或者 被cancel 了,总之非NEW状态的任务,线程就不处理了。
    //条件二:!UNSAFE.compareAndSwapObject(this, runnerOffset,null, Thread.currentThread())
    //       条件成立: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.run 代码块执行成功 未抛出异常
            //false 表示callable.run 代码块执行失败 抛出异常
            boolean ran;

            try {
                //调用程序员自己实现的callable 或者 装饰后的runnable
                result = c.call();
                //c.call未抛出任何异常,ran会设置为true 代码块执行成功
                ran = true;
            } catch (Throwable ex) {
                //说明程序员自己写的逻辑块有bug了。
                result = null;
                ran = false;
                setException(ex);
            }

            if (ran)
                //说明当前c.call正常执行结束了。
                //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)
            //回头再说..讲了 cancel() 就明白了。
            handlePossibleCancellationInterrupt(s);
    }
}

该方法的逻辑流程如下:

  • 如果当前state属性不是NEWNEW表示当前任务尚未执行)并且通过cas修改属性runner(表示当前执行任务的线程引用)的值为当前线程失败的话,直接返回,说明已经有其他线程已经执行该任务或者正在执行该任务。
  • 如果上面没有满足的话,则真正执行咱们编写代码中的任务逻辑c.call();,执行有可能成功,也有可能失败,执行成功的话,接着执行set(result);,出现异常的话接着执行setException(ex);

接着查看set(result);如下:

protected void set(V v) {
    //使用CAS方式设置当前任务状态为 完成中..
    //有没有可能失败呢? 外部线程等不及了,直接在set执行CAS之前 将  task取消了。  很小概率事件。
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {

        outcome = v;
        //将结果赋值给 outcome之后,马上会将当前任务状态修改为 NORMAL 正常结束状态。
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state

        //猜一猜?
        //最起码得把get() 再此阻塞的所有线程 唤醒..
        finishCompletion();
    }
}

set方法的逻辑流程:首先通过cas修改state属性值为COMPLETINGCOMPLETING: 当前任务正在结束,稍微完全结束,一种临界状态),接着将任务执行结果赋值给outcome,然后再将state属性值为NORMALNORMAL:当前任务正常结束),最后执行finishCompletion方法,都不用猜,finishCompletion方法肯定是将之前因为调用get()获取不到任务结果而阻塞的线程唤醒

接着查看finishCompletion方法,如下:

/**
 * 将之前因为调用get()方法阻塞的所有线程都唤醒
 */
private void finishCompletion() {
    // assert state > COMPLETING;
    //q指向waiters 链表的头结点。
    for (WaitNode q; (q = waiters) != null;) {

        //使用cas设置 waiters 为 null 是因为怕 外部线程使用 cancel 取消当前任务 也会触发finishCompletion方法。 小概率事件。
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
            for (;;) {
                //获取当前node节点封装的 thread
                Thread t = q.thread;
                //条件成立:说明当前线程不为null
                if (t != null) {

                    q.thread = null;//help GC
                    //唤醒当前节点对应的线程
                    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 helpGC
    callable = null;        // to reduce footprint
}

finishCompletion方法的流程:将属性waiters表示因为调用get方法阻塞的线程队列头节点)的值通过cas设置为null,接着遍历所有的WaitNode(WaitNode:为因为调用get方法阻塞的线程的封装结构),调用LockSupport.unpark(t);唤醒所有阻塞的线程。

get方法

我们都知道可以调用FutureTaskget方法获取任务执行结果,很有可能多个线程调用该方法,该方法源码如下:

//场景:多个线程等待当前任务执行完成后的结果...
public V get() throws InterruptedException, ExecutionException {
    //获取当前任务状态
    int s = state;
    //条件成立:未执行、正在执行、正完成。 调用get的外部线程会被阻塞在get方法上。
    if (s <= COMPLETING)
        //核心方法:awaitDone
        //返回task当前状态,可能当前线程在里面已经睡了一会了..
        s = awaitDone(false, 0L);
    //获取任务执行结果
    return report(s);
}

get方法执行流程如下:

  • 如果当前state属性的值小于或者等于COMPLETINGCOMPLETING表示当前任务正在结束,稍微完全结束,一种临界状态),即表示当前任务该没计算出结果,所以调用get方法的线程需要执行awaitDone方法阻塞,等到执行任务的线程执行上面的run方法完成后将其唤醒。
  • 如果当前state属性的值大于COMPLETING,表示当前任务已经执行结束了,接着执行report方法获取任务执行结果。

我们接着看awaitDone方法,源码如下:

private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
    //0 不带超时
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    //引用当前线程 封装成 WaitNode 对象
    WaitNode q = null;
    //表示当前线程 waitNode对象 有没有 入队/压栈
    boolean queued = false;
    //自旋
    for (;;) {

        //条件成立:说明当前线程唤醒 是被其它线程使用中断这种方式喊醒的。interrupted()
        //返回true 后会将 Thread的中断标记重置回false.
        if (Thread.interrupted()) {
            //当前线程node出队
            removeWaiter(q);
            //get方法抛出 中断异常。
            throw new InterruptedException();
        }

        //假设当前线程是被其它线程 使用unpark(thread) 唤醒的话。会正常自旋,走下面逻辑。

        //获取当前任务最新状态
        int s = state;
        //条件成立:说明当前任务 已经有结果了.. 可能是好 可能是 坏..
        if (s > COMPLETING) {

            //条件成立:说明已经为当前线程创建过node了,此时需要将 node.thread = null helpGC
            if (q != null)
                q.thread = null;
            //直接返回当前状态.
            return s;
        }
        //条件成立:说明当前任务接近完成状态...这里让当前线程再释放cpu ,进行下一次抢占cpu。
        else if (s == COMPLETING) // cannot time out yet
            Thread.yield();
        //条件成立:第一次自旋,当前线程还未创建 WaitNode 对象,此时为当前线程创建 WaitNode对象
        else if (q == null)
            q = new WaitNode();
        //条件成立:第二次自旋,当前线程已经创建 WaitNode对象了,但是node对象还未入队
        else if (!queued){
            //当前线程node节点 next 指向 原 队列的头节点   waiters 一直指向队列的头!
            q.next = waiters;
            //cas方式设置waiters引用指向 当前线程node, 成功的话 queued == true 否则,可能其它线程先你一步入队了。
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset, waiters, q);
        }
        //第三次自旋,会到这里。
        else if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                removeWaiter(q);
                return state;
            }
            LockSupport.parkNanos(this, nanos);
        }
        else
            //当前get操作的线程就会被park了。  线程状态会变为 WAITING状态,相当于休眠了..
            //除非有其它线程将你唤醒  或者 将当前线程 中断。
            LockSupport.park(this);
    }
}

awaitDone方法执行流程如下:

  • 首先进来for自旋,第一次肯定是先满足q == null条件,执行q = new WaitNode();创建与当前线程相关的WaitNode节点。
  • 接着第二次自旋,满足!queuedtrue条件,使用头插法(即先创建的WaitNode为等待队列的头节点)加入到等待队列,然后通过cas操作替换之前的waiters等待队列。
  • 第三次自旋,则会调用LockSupportpark方法将当前调用get方法想要获取任务执行结果的线程阻塞。
  • 直到任务执行完成,调用上面的finishCompletion方法将阻塞队列中的线程唤醒,接着再次自旋,就会满足s > COMPLETING这个条件,因为任务已经执行完成,awaitDone方法返回。

接着我们来看看report方法获取任务执行结果,源码如下:

private V report(int s) throws ExecutionException {
    //正常情况下,outcome 保存的是callable运行结束的结果
    //非正常,保存的是 callable 抛出的异常。
    Object x = outcome;
    //条件成立:当前任务状态正常结束
    if (s == NORMAL)
        //直接返回callable运算结果
        return (V)x;

    //被取消状态
    if (s >= CANCELLED)
        throw new CancellationException();

    //执行到这,说明callable接口实现中,是有bug的...
    throw new ExecutionException((Throwable)x);
}

很简单,就是将任务执行结果outcome赋值给x,然后如果任务是正常执行结果的话,即满足(s == NORMAL),则直接返回任务执行结果。否则就是可能任务被取消或者执行出现异常了,抛出相应的异常即可。

cancel方法

我们知道任务被其他的线程正在执行的时候,当前线程也可以调用cancel方法取消任务,该方法源码如下:

public boolean cancel(boolean mayInterruptIfRunning) {
    //条件一:state == NEW 成立 表示当前任务处于运行中 或者 处于线程池 任务队列中..
    //条件二:UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED))
    //      条件成立:说明修改状态成功,可以去执行下面逻辑了,否则 返回false 表示cancel失败。
    if (!(state == NEW &&
            UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                    mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;

    try {    // in case call to interrupt throws exception
        if (mayInterruptIfRunning) {
            try {
                //执行当前FutureTask 的线程,有可能现在是null,是null 的情况是: 当前任务在 队列中,还没有线程获取到它呢。。
                Thread t = runner;
                //条件成立:说明当前线程 runner ,正在执行task.
                if (t != null)
                    //给runner线程一个中断信号.. 如果你的程序是响应中断 会走中断逻辑..假设你程序不是响应中断的..啥也不会发生。
                    t.interrupt();
            } finally { // final state
                //设置任务状态为 中断完成。
                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
            }
        }

    } finally {
        //唤醒所有get() 阻塞的线程。
        finishCompletion();
    }

    return true;
}

cancel方法执行流程:将当前正在执行任务的线程,即runner线程一个中断信号(t.interrupt();),接着将任务状态state的值通过cas的方式修改为INTERRUPTED(INTERRUPTED表示当前任务已中断),最后还是跟run方法一样调用finishCompletion方法唤醒因为调用get方法而阻塞的所有线程。