JUC框架 FutureTask源码解析 JDK8

前言

FutureTask的使用方法已经在上一篇进行了讲解,其实它和SynchronousQueue很像,执行task的线程是生产者,获取执行结果的线程是消费者,消费者阻塞的原因不是生产者还没来,是因为生产者还没有生产出来执行结果。只不过,这里只有一个生产者(FutureTask对象),却可以对应到多个消费者(对同一个FutureTask对象调用get的不同线程)。

为了方便称呼,本文以生产者和消费者来指代两种线程。

JUC框架 系列文章目录

状态

FutureTask的重点在于对task(Callable.call())的执行的管理,而FutureTask通过一个volatile的int值来管理task的执行状态。

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;

这个state就和AQS的state一样,是最重要的属性,因为state就时刻反映了task的执行状态。

     * Possible state transitions:
     * NEW -> COMPLETING -> NORMAL
     * NEW -> COMPLETING -> EXCEPTIONAL
     * NEW -> CANCELLED
     * NEW -> INTERRUPTING -> INTERRUPTED

可能状态转移过程是上面这4种(朋友,听说过状态机吗)
在这里插入图片描述
初始化时FutureTask的state是NEW,这是构造器保证的。

这种状态的转换都是不可逆的,某些过程中还可能存在中间状态,但这种中间状态存在的时间很短,且马上也会变成相应的最终状态。所以可以认为,只要状态不是NEW的话,就可以认为生产者执行task已经完毕。关于这一点,isDone函数可以替我作证:

    public boolean isDone() {
    
    
        return state != NEW;
    }

调用这些函数可以使得状态发生转移(具体过程后面讲解):

  • set(V v)使得NEW -> COMPLETING -> NORMAL。
  • setException(Throwable t)使得NEW -> COMPLETING -> EXCEPTIONAL。
  • cancel(boolean mayInterruptIfRunning)可能有两种状态转移:
    • mayInterruptIfRunning为false时,使得NEW -> CANCELLED。
    • mayInterruptIfRunning为true时,使得NEW -> INTERRUPTING -> INTERRUPTED。

注意,图中的“取消”二字打了引号,因为消费者实际上不可能使得正在执行的生产者线程咔嚓终止掉。

消费者链表

前面提到,对同一个FutureTask对象调用get的不同线程的都属于消费者,当生产者还没有执行完毕task时,调用get会阻塞。而做法是将消费者线程包装成一个链表节点,放到一个链表中,等到task执行完毕,再唤醒链表中的每个节点的线程。这种做法类似于AQS的条件队列和signalAll

反正最终链表上的所有节点都将被唤醒,所以链表是栈的逻辑结构,这样只用保存栈顶head指针,稍微简单一点。

static final class WaitNode {
    
    
    volatile Thread thread;
    volatile WaitNode next;
    WaitNode() {
    
     thread = Thread.currentThread(); }
}

从上面链接节点的成员来看,确实很简单,只需要保存线程对象和next指针即可。

成员

	/** task执行状态 */
    private volatile int state;
    /** task */
    private Callable<V> callable;
    /** 执行结果,可能是泛型类型V 或 抛出的异常。这都是前两种状态转移才会设置的 */
    private Object outcome; // non-volatile的,因为最终会对state进行CAS操作,从而保证可见性
    /** 执行task的生产者 */
    private volatile Thread runner;
    /** 消费者栈的head指针 */
    private volatile WaitNode waiters;
  • outcome是Object类型,可以存任何类型对象。这样既可以存泛型类型V,也可以存异常对象。
  • 当调用new Thread(FutureTask对象).start()时,生产者线程便创建并开始运行了,并且会在FutureTask#run()的刚开始就把生产者线程存放到runner中。
  • 当调用FutureTask对象.get()时,如果task还未执行完毕,当前消费者线程会被包装成一个节点扔到栈中去。

构造器

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。除了可以接受Callable之外,还可以接受Runnable,但也是马上通过适配器模式把Runnable包装成一个Callable而已。

实现Runnable接口

在这里插入图片描述
先来看一看FutureTask对Runnable接口的实现。

public void run() {
    
    
	// 执行run函数的前提是state为NEW,且生产者位置还没有被占用
    if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
        return;
    //执行到这里,说明此时state为NEW,且生产者位置已经被当前线程占领
    try {
    
    
        Callable<V> c = callable;
        if (c != null && state == NEW) {
    
      //直到这里,还会检查一次state是否为NEW
            V result;
            boolean ran;
            try {
    
    
                result = c.call();  // 执行task
                ran = true;
            } catch (Throwable ex) {
    
      // 生产者自身执行task过程中,抛出了异常
                result = null;
                ran = false;
                setException(ex);
            }
            if (ran)  // 生产者顺利执行完了task
                set(result);
        }
    } finally {
    
    
        // 执行完毕释放生产者线程对象引用
        // FutureTask对象.run() 不可能被调用第二次,因为此时state肯定不是NEW了,run方法的第一句肯定通不过
        runner = null;
        // 消费者线程可能使得task取消,其中一种状态转移是NEW -> INTERRUPTING -> INTERRUPTED
        // 这种转移有中间状态,需要检测这种中间状态变成最终状态
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

在函数的一开始,需要检测state是否为NEW,且当前线程对象需要占领runner,并且在退出run函数之前,一直都会占领着runner

if (c != null && state == NEW)会再检测一次state是否为NEW,如果为NEW,就开始执行task。生产者自己执行task时(c.call()),有两种情况:

  • 顺利执行完task,然后调用set(result)
  • 执行task途中抛出异常,然后调用setException(ex)
    protected void set(V v) {
    
    
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
    
    
            outcome = v;
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }

如果顺利执行完task,outcome会被赋值为执行结果。另外,状态转移是NEW -> COMPLETING -> NORMAL的过程,并且这个中间状态是只存在很短的时间的。

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

如果执行task途中抛出异常,outcome会被赋值为抛出的异常对象。另外,状态转移是NEW -> COMPLETING -> EXCEPTIONAL的过程,并且这个中间状态是只存在很短的时间的。

上面两个函数都调用了finishCompletion

    private void finishCompletion() {
    
    
        // 保证调用此函数时,state > COMPLETING;
        for (WaitNode q; (q = waiters) != null;) {
    
    
            if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
    
    
                for (;;) {
    
    
                    Thread t = q.thread;
                    if (t != null) {
    
    
                        q.thread = null;
                        LockSupport.unpark(t);
                    }
                    WaitNode next = q.next;
                    if (next == null)
                        break;
                    q.next = null; // unlink to help gc
                    q = next;
                }
                break;
            }
        }

        done();

        callable = null;        // to reduce footprint
    }

此函数负责唤醒所有消费者线程,原理很简单,内层循环遍历链表的每个节点,唤醒每个节点的线程对象。而外层循环在刚开始时,负责给局部变量q赋值,在退出外层循环时,负责检查waiters是否已经被赋值为null(当然检查结果肯定成立)。

最后还会调用done函数,但这只是空实现,这是用来给使用者拓展用的,可以让生产者线程在执行完毕前多做一点善后工作。

    } finally {
    
    
        // 执行完毕释放生产者线程对象引用
        // FutureTask对象.run() 不可能被调用第二次,因为此时state肯定不是NEW了,run方法的第一句肯定通不过
        runner = null;
        // 消费者线程可能使得task取消,其中一种状态转移是NEW -> INTERRUPTING -> INTERRUPTED
        // 这种转移有中间状态,需要检测这种中间状态变成最终状态
        int s = state;
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }

最后还有finally块,先执行runner = null,执行后不用担心FutureTask对象.run()被调用两次,因为此时state肯定不是NEW了,run方法的第一句肯定通不过。

再判断当前是否为>= INTERRUPTING,这种情况可能是遇到了state的中间状态,所以需要调用handlePossibleCancellationInterrupt自旋等待直到最终状态。

private void handlePossibleCancellationInterrupt(int s) {
    
    
    if (s == INTERRUPTING)
        while (state == INTERRUPTING)
            Thread.yield(); 
}

但是我们从前面的讲解从来没见到过有把state设置为INTERRUPTING的操作,其实这个操作是消费者线程干的,接下来将讲解。

实现Future接口

再来看一看FutureTask对Future接口的实现。

普通get、超时get

    public V get() throws InterruptedException, ExecutionException {
    
    
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }

此函数判断当前state,并根据情况调用awaitDone进行阻塞等待。

  • 如果state为NEW,那么生产者还没开始执行呢,肯定得阻塞等待。
  • 如果state为COMPLETING,那么生产者线程马上执行完了,并且是 生产者正常执行完的过程(即前两种状态转移),那么也阻塞等待,即使马上会被唤醒。
    public V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
    
    
        if (unit == null)
            throw new NullPointerException();
        int s = state;
        if (s <= COMPLETING &&
            (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
            throw new TimeoutException();
        return report(s);
    }

而这个函数是get的超时版本,所以调用awaitDone的实参设置不一样。并且在退出awaitDone函数时,要检查返回值。返回值总是state的最新值:

  • 如果state为NEW,那么说明返回是因为超时,因为生产者执行task期间state一直都为NEW(直到执行了setsetException才会改变),所以说明返回时,要么生产者还没执行完task,要么生产者根本还没开始执行
  • 如果state为COMPLETING,那么说明生产者即将执行完毕,但还没有设置返回值。虽然运气不好,但也只好算作超时。

来看看awaitDone是怎么做到阻塞等待和超时版本的阻塞等待的:

    private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
    
    
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false; //代表入栈成功
        for (;;) {
    
    
            if (Thread.interrupted()) {
    
      //消费者线程被中断
                removeWaiter(q);
                throw new InterruptedException();
            }

            int s = state;  //获取最新的state
            //分为两种情况:
            //1. NORMAL或EXCEPTIONAL,生产者正常执行完task
            //2. 其余情况,其他消费者取消了task
            if (s > COMPLETING) {
    
    
                if (q != null)
                    q.thread = null;
                return s;
            }
            else if (s == COMPLETING) // 正常执行完的中间状态,自旋等待
                Thread.yield();
            else if (q == null)  // state为NEW,说明生产者线程还没执行完
                q = new WaitNode();
            else if (!queued)  // 节点已经创建,但还没入栈
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,//CAS保证了入栈的正确性
                                                     q.next = waiters, q);//先把新节点q的next指向旧栈顶,然后更新栈顶为q
            // 此时节点已经入队,但是state还是NEW,只能阻塞等待了
            else if (timed) {
    
      //如果是超时版本
                nanos = deadline - System.nanoTime();  //获得剩余时间
                if (nanos <= 0L) {
    
      //剩余时间小于等于0,说明已经超时,但task还没执行完。
                    removeWaiter(q);
                    return state; //注意这里返回的不是局部变量,而是最新值。运气好的话,可能返回的不是NEW
                }
                LockSupport.parkNanos(this, nanos);
            }
            else
                LockSupport.park(this);
        }
    }
  • 刚开始检查消费者线程的中断状态,如果被中断,说明消费者线程不应该再等待了。那么从栈中移除节点,并抛出中断异常。比如消费者线程在LockSupport.park(this)后被中断而唤醒。
  • 接下来检查当前state是什么(可能是 第一次循环执行到这里,也可能是 阻塞后被唤醒下一次循环执行到这里),分为两种情况:
    • 如果是NORMAL或EXCEPTIONAL,说明生产者正常执行完task,没有受到消费者的取消动作干扰。这两种都是最终状态,直接返回即可。
    • 如果是CANCELLED、INTERRUPTING、INTERRUPTED,那么说明别的消费者“取消”了task。其中有一种中间状态,这无所谓,因为这三种状态都代表了取消。
    • 另外,这里是return s返回局部变量,而不是return state返回最新成员。因为大部分状态都是最终状态,即使有一个中间状态,它的最终状态也是已知的了。
  • 如果生产者正在设置执行结果,那么自旋等待。
  • 接下来就是正常阻塞前的流程:
    • 发现state还是NEW,所以新建节点。
    • 新建节点后,发现还没有入队,那么入队。
    • 入队完毕后,当前线程就可能马上阻塞了。
  • 阻塞根据参数有两种版本:
    • 无限阻塞。直接调用LockSupport.park(this)
    • 超时阻塞。先计算得到剩余时间还有多少(正常阻塞前的流程也会花点时间的),然后调用LockSupport.parkNanos(this, nanos)。注意,如果超时阻塞后因为超时而唤醒时,也会走到这里,然后发现已经超时,那么栈中移除节点,并返回最新state。
      • 注意,返回的是return state,这里其实除了返回NEW以外,其他state也是都是可能返回的,在调用removeWaiter(q)期间可能会发现一些事情。比如,在此期间,生产者线程正在执行set,那么state可能是COMPLETING或NORMAL;比如,在此期间,别的消费者线程正在执行cancel,那么state可能是CANCELLED、INTERRUPTING、INTERRUPTED。
      • 关于上面这一点,其实就是给已经超时的超时操作多个机会,说不定执行完removeWaiter(q),state就变成NORMAL了呢。

来看一下removeWaiter(q)是怎么移除节点的,注意实参是可能为null的,当q局部变量还没创建,当前线程就被中断时。

    private void removeWaiter(WaitNode node) {
    
    
    	// 如果实参为null,那么啥也不做
        if (node != null) {
    
    
            node.thread = null; // 把node的thread设置为null作为标记。注意,此后node再无作用
            retry:
            for (;;) {
    
              // 致力于寻找pred -> q(thread == null) -> s的结构
                for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
    
    
                    s = q.next;
                    if (q.thread != null)  //发现q是未取消节点,更新pred和q
                        pred = q;
                    // 发现q是取消节点,但有两种情况
                    else if (pred != null) {
    
      // 如果q不是首节点
                        pred.next = s;  //将pred -> q -> s变成 pred -> s
                        if (pred.thread == null) // 如果发现pred也是一个取消节点,这说明q还在链表中
                            continue retry;
                    }
                    else if (!UNSAFE.compareAndSwapObject(this, waitersOffset,  //如果q是首节点
                                                          q, s))  //那就使得栈顶下移一个节点即可
                        continue retry;
                }
                break;
            }
        }
    }

此函数参数node的thread被标记为null后,node就没有作用了。因为遍历过程中,我们是去寻找所有的被标记的节点,然后尝试移除它们。

  • 如果发现一个取消节点是首节点,那么使得head下移一个节点即可。
  • 如果发现一个取消节点不是首节点,那么将pred -> q -> s变成pred -> s(执行pred.next = s)。如下图所示,从链表任意节点出发,都不能到达这个q节点。
    在这里插入图片描述
  • 不过有时候执行pred.next = s其实是一个无效操作,实际上并没有把q移除,此时需要重新开始循环continue retry(关于这一点,LinkedTransferQueue#unsplice也有一样的判断)。这种情况是pred是一个取消节点时,如下图所示,q节点还是处于链表中:
    在这里插入图片描述
    最后,我们再回到两个get函数的report(s),分析report()函数之前,先看看这个实参s可能是什么值。
  • 首先这个值是awaitDone调用后返回的。
  • 如果是无限阻塞地调用awaitDone,那么只可能返回s > COMPLETING的值。
  • 如果是超时阻塞地调用awaitDone,虽然可能返回NEW或COMPLETING,但是在get(long timeout, TimeUnit unit)中会马上抛出超时异常的。
  • 总之,实参s只能是s > COMPLETING的值。
    private V report(int s) throws ExecutionException {
    
    
        Object x = outcome;//不管当前outcome是不是null,直接赋值
        if (s == NORMAL)//如果状态是NORMAL,则outcome肯定不是null
            return (V)x;
        if (s >= CANCELLED)//如果状态是取消的状态(这里其实不可能是INTERRUPTING)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);//如果状态是EXCEPTIONAL,那么抛出这个异常对象
    }

cancel、isCancelled

之前说过,消费者实际上不可能使得正在执行的生产者线程咔嚓终止掉,接下来将解释。我们先来回顾一下,生产者线程在run函数中,直到执行setsetException之前,都在正常执行task中,而既然没有执行这两个函数,说明这段时间state还是为NEW的。

    public boolean cancel(boolean mayInterruptIfRunning) {
    
    
        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函数执行前提就是state是NEW,在生产者线程执行setsetException之前,都是可以CAS成功的。

如果消费者是在生产者线程执行run方法的if (c != null && state == NEW)之前就执行了cancel函数,那么才可以终止生产者执行task。

如果消费者是在生产者线程执行run方法的if (c != null && state == NEW)之后才执行的cancel函数,那么将不能终止生产者。

  • 如果参数是false,state从NEW修改为CANCELLED。但修改state,并不能使得生产者线程运行终止。
  • 如果参数是true,state从NEW修改为INTERRUPTING,中断生产者线程后,再修改为INTERRUPTED。我们知道,中断一个正在运行的线程,线程运行状态不会发生变化的,只是会设置一下线程的中断状态。也就是说,这也不能使得生产者线程运行终止。除非生产者线程运行的代码(Callable.call())时刻在检测自己的中断状态。

那你可能会问,这种情况既然不能真的终止生产者线程,那么这个cancel函数有什么用,其实还是有用的:

  • 如果参数为true,那么会去中断生产者线程。但生产者线程能否检测到,取决于生产者线程运行的代码(Callable.call())。
  • 状态肯定会变成CANCELLED或INTERRUPTED,新来的消费者线程会直接发现,然后在get函数中不去调用awaitDone
  • 对于生产者线程来说,执行task期间不会影响。但最后执行setsetException,会发现这个state,然后不去设置outcome

最后执行了finishCompletion函数,唤醒所有的消费者线程。

另外,注意,多个消费者来调用cancel函数,最多只有一个能够成功,即返回true。

    public boolean isCancelled() {
    
    
        return state >= CANCELLED;
    }

这三种状态都属于被取消了。

isDone

    public boolean isDone() {
    
    
        return state != NEW;
    }

只要状态不是NEW的话,就可以认为生产者执行task已经完毕,或者已经被取消(取消的三种情况,上面也讲了)。

普通写和CAS写混合

在实现中可以看到有时候使用普通写的语义,有时候使用CAS写。

    public boolean cancel(boolean mayInterruptIfRunning) {
    
    
        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函数中的UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED)这里使用的普通写,其他线程可能不能马上看到,但这没关系。因为:

  • 一来,这个状态转移是唯一的。INTERRUPTING只能变成INTERRUPTED。其他线程暂时看不到 INTERRUPTED 也没关系。(注意,暂时看不到 INTERRUPTING,会导致handlePossibleCancellationInterrupt自旋)
  • 二来,finishCompletion中也有对其他volatile字段的CAS写操作。这样做会把之前的普通写都刷新到内存中去。

总结

  • FutureTask整合了Callable对象,使得我们能够异步地获取task执行结果。
  • 执行FutureTask.run()的线程就相当于生产者,生产出执行结果给outcome。执行FutureTask.get()的线程就相当于消费者,它们会阻塞等待直到执行结果产生。
  • 如果生产者线程已经开始执行Callable.call(),那么消费者调用cancel,实际上是无法终止生产者的运行的。

猜你喜欢

转载自blog.csdn.net/anlian523/article/details/108029564