JUC框架 CompletableFuture源码解析 JDK8

文章目录

  • 前言
  • 基础设施
    • 创建CompletableFuture
    • CompletableFuture成员
    • Completion内部类
    • AltResult内部类
    • Signaller内部类
  • 从supplyAsync + thenApply(thenApplyAsync)理解
    • supplyAsync
    • thenApply(thenApplyAsync)
    • UniApply内部类#tryFire
    • CompletableFuture#uniApply
    • 谁执行了当前stage,谁负责处理后续stage
    • Async任务的执行过程
    • CompletableFuture#postComplete 树形结构
      • postComplete可以被并发调用
    • CompletableFuture#postFire
    • CompletableFuture#cleanStack
    • thenApplyAsync另起线程
  • 其他方法与thenApply的区别
    • 普通方法(依赖的CompletableFuture个数为1个或者2个)
    • allOf
    • anyOf
  • get相关方法
    • get()
    • join()
    • 超时get
  • cancel相关方法
    • 前提为result为null
    • 没有前提
  • 总结

前言

我们知道FutureTask实现了task异步执行,但对于执行结果的获取,如果异步执行还在进行中,那么线程只能get阻塞等待,或者轮询isDone,这两种方式都和我们开始实现异步的初衷相违背。所以就诞生了这个CompletableFuture,它的最大不同之处在于,通过提供回调函数的概念,把处理执行结果的过程也放到异步线程里去做。

JUC框架 系列文章目录

基础设施

CompletableFuture实现了Future接口和CompletionStage接口,CompletionStage接口提供了很多异步回调的函数。

创建CompletableFuture

有两种方法可以创建CompletableFuture:

  • 静态方法,比如supplyAsync。属于零输入,执行时机是马上执行。
  • 成员方法,比如CompletableFuture对象.thenApply。属于有输入,执行时机是调用对象的完成时机。

CompletableFuture成员

    volatile Object result;       // Either the result or boxed AltResult
    volatile Completion stack;    // Top of Treiber stack of dependent actions

CompletableFuture是在用户使用过程中唯一能直接接触到的对象。

  • result存放执行结果,正常结果或者抛出的异常都要存放,所以是Object。任务执行完毕后,result会变成非null。
  • stack是一个链栈,存放与this对象直接关联的Completion对象。Completion对象是用来驱动某一个CompletableFuture对象,所谓的驱动,就是使得这个CompletableFuture对象的result成员变为非null。

Completion内部类

Completion对象是用户接触不到的,它用来驱动CompletableFuture对象。

abstract static class Completion extends ForkJoinTask<Void> implements Runnable, AsynchronousCompletionTask {...}
  • 它继承了ForkJoinTask,但也仅仅是为了套上ForkJoinTask的壳子,因为CompletableFuture默认的线程池是ForkJoinPool.commonPool()
  • 但它也实现了Runnable,这使得它也能被一个普通线程正常执行。
  • Completion有很多继承的子类,它们分别实现了tryFire方法。

AltResult内部类

    static final class AltResult { // See above
        final Throwable ex;        // null only for NIL
        AltResult(Throwable x) { this.ex = x; }
    }

    static final AltResult NIL = new AltResult(null);

前面提到,任务执行完毕后,result会变成非null。但如果执行结果就是null该怎么办。所以用这个对象来包装一下null。

Signaller内部类

    static final class Signaller extends Completion
        implements ForkJoinPool.ManagedBlocker {
        long nanos;                    // wait time if timed
        final long deadline;           // non-zero if timed
        volatile int interruptControl; // > 0: interruptible, < 0: interrupted
        volatile Thread thread;
        ...
   }

配合get或者join使用的,实现对 想获取执行结果的线程 的阻塞和唤醒的功能。

从supplyAsync + thenApply(thenApplyAsync)理解

CompletableFuture实现了CompletionStage,代表一个执行阶段,我们可以在执行阶段之后添加后续任务,当前一个执行阶段完毕时,马上触发后续任务。

    public static void test() {
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            String supplyAsyncResult = " "+Thread.currentThread().getName()+" Hello world! ";
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(supplyAsyncResult);
            return supplyAsyncResult;
        }).thenApply(r -> {  //添加后续任务
            String thenApplyResult = Thread.currentThread().getName()+r + " thenApply! ";
            System.out.println(thenApplyResult);
            return thenApplyResult;
        });

        try {
            System.out.println(completableFuture.get() + " finish!");
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
/*output:
 ForkJoinPool.commonPool-worker-9 Hello world! 
ForkJoinPool.commonPool-worker-9 ForkJoinPool.commonPool-worker-9 Hello world!  thenApply! 
ForkJoinPool.commonPool-worker-9 ForkJoinPool.commonPool-worker-9 Hello world!  thenApply!  finish!
*/

首先注意到这是一种链式编程,supplyAsync返回的是一个CompletableFuture对象(代表一个执行阶段),然后在这个CompletableFuture对象上再执行thenApply,又返回了一个新的CompletableFuture对象(代表下一个执行阶段)。而且发现,两个task都是在另外的线程里执行的,这完全实现了异步处理的效果。

为了方便称呼,我们叫第一个task为 前一个stage,第二个task为 当前stage

本文也会把CompletableFuture对象称为一个stage。

supplyAsync

    public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
        return asyncSupplyStage(asyncPool, supplier);
    }

    static <U> CompletableFuture<U> asyncSupplyStage(Executor e,
                                                     Supplier<U> f) {
        if (f == null) throw new NullPointerException();
        CompletableFuture<U> d = new CompletableFuture<U>();
        e.execute(new AsyncSupply<U>(d, f));
        return d;
    }

可见这个CompletableFuture对象是new出来以后就直接返回的,但是刚new的CompletableFuture对象的result成员是为null,因为task还没有执行完。而task的执行交给了e.execute(new AsyncSupply(d, f))

    static final class AsyncSupply<T> extends ForkJoinTask<Void>
            implements Runnable, AsynchronousCompletionTask {
        CompletableFuture<T> dep; Supplier<T> fn;
        AsyncSupply(CompletableFuture<T> dep, Supplier<T> fn) {
            this.dep = dep; this.fn = fn;
        }

        public final Void getRawResult() { return null; }
        public final void setRawResult(Void v) {}
        public final boolean exec() { run(); return true; }

        public void run() {
            CompletableFuture<T> d; Supplier<T> f;
            if ((d = dep) != null && (f = fn) != null) {
                dep = null; fn = null;  //为了防止内存泄漏,方便GC.同时dep为null也是一种代表当前Completion对象的关联stage已完成的标志
                if (d.result == null) {
                    try {
                        d.completeValue(f.get());  //执行task
                    } catch (Throwable ex) {       //执行task期间抛出了异常
                        d.completeThrowable(ex);
                    }
                }
                d.postComplete();
            }
        }
    }

很显然,为了能够e.execute,AsyncSupply也必须是一个Runnable对象。执行e.execute(new AsyncSupply(d, f)),run函数就会被另一个线程执行。当task被异步执行完毕后,会调用completeValuecompleteThrowable来为result成员赋值。
JUC框架 CompletableFuture源码解析 JDK8_第1张图片
上图体现了supplyAsync()的过程,对于调用者来说,只能接触到stage对象,并且调用者根本不知道stage对象何时能产生运行结果。对于实现来说,把task包装成一个AsyncSupply对象,另起线程执行task,执行完毕后为stage对象赋值运行结果。

注意,stage完成的标志,就是它的result成员非null。

thenApply(thenApplyAsync)

supplyAsync直接返回了个CompletableFuture对象后,主线程在这个对象上调用thenApplythenApplyAsync将后续stage接续到前一个stage的后面。

    public <U> CompletableFuture<U> thenApply(
        Function<? super T,? extends U> fn) {
        return uniApplyStage(null, fn);
    }

    public <U> CompletableFuture<U> thenApplyAsync(
        Function<? super T,? extends U> fn) {
        return uniApplyStage(asyncPool, fn);
    }

thenApply不会传入Executor,因为它优先让当前线程(例子中是main线程)来执行后续stage的task。具体的说:

  • 当发现前一个stage已经执行完毕时,直接让当前线程来执行后续stage的task。
  • 当发现前一个stage还没执行完毕时,则把当前stage包装成一个UniApply对象,放到前一个stage的栈中。执行前一个stage的线程,执行完毕后,接着执行后续stage的task。
  • 总之,要么是一个异步线程走到底,要么让当前线程来执行后续stage(因为异步线程已经结束,而且你又没有给Executor,那只好让当前线程来执行咯)。

thenApplyAsync会传入一个Executor,因为它总是让 Executor线程池里面的线程 来执行后续stage的task。具体的说:

  • 当发现前一个stage已经执行完毕时,直接让Executor来执行。
  • 当发现前一个stage还没执行完毕时,则等到执行前一个stage的线程执行完毕后,再让Executor来执行。
  • 总之,无论哪种情况,执行后一个stage的线程肯定不是当前添加后续stage的线程(例子中是main线程)了。
private <V> CompletableFuture<V> uniApplyStage(
    Executor e, Function<? super T,? extends V> f) {
    if (f == null) throw new NullPointerException();
    CompletableFuture<V> d =  new CompletableFuture<V>();
    //如果e不为null,说明当前stage是无论如何都需要被异步执行的。所以短路后面的d.uniApply。
    //如果e为null,说明当前stage是可以允许被同步执行的。所以需要尝试一下d.uniApply。
    if (e != null || !d.uniApply(this, f, null)) {
    	//进入此分支有两种情况:
    	//1. 要么e不为null,前一个stage不一定执行完毕。就算前一个stage已经执行完毕,还可以用e来执行当前stage
    	//2. 要么e为null,但前一个stage还没执行完毕。所以只能入栈等待
        UniApply<T,V> c = new UniApply<T,V>(e, d, this, f);
        push(c);
        //(考虑e为null)入栈后需要避免,入栈后刚好前一个stage已经执行完毕的情况。这种特殊情况,如果不执行c.tryFire(SYNC),当前stage永远不会完成。
        //(考虑e不为null)入栈后需要避免,入栈前 前一个stage已经执行完毕的情况。
        //下面这句,有可能发现前一个stage已经执行完毕,然后马上执行当前stage
        c.tryFire(SYNC);
    }
    return d;
}
  • CompletableFuture d = new CompletableFuture()return d来看,还是和之前一样,new出来一个CompletableFuture对象后就尽快返回。
  • 如果Executor e为null(当前stage是可以允许被同步执行的),并且此时前一个stage已经结束了,这种情况应该让当前线程来同步执行当前stage。但我们其实不知道前一个stage是否结束,所以通过d.uniApply(this, f, null)检测前一个stage是否已经结束。如果d.uniApply(this, f, null)返回true,说明发现了前一个stage已经结束,并且当前线程执行完毕当前stage,所以这种情况就会直接return d
    • d.uniApply(this, f, null)的第三个实参为null,这代表与当前stage相关联的Completion对象还没有入栈(还没push(c)),即不可能有别的线程与当前线程来竞争执行当前stage。这样d.uniApply(this, f, null)里面的逻辑就变简单了,要么发现前一个stage还没执行完,直接返回false;要么发现前一个stage执行完毕,那么执行当前stage后,返回true。

进入分支有两种情况:

  • 如果e不为null:
    • 如果前一个stage已经执行完毕:当前线程在c.tryFire(SYNC)中把接管的当前stage转交给e执行。
    • 如果前一个stage还没执行完毕:当前线程会直接返回,等到执行前一个stage的线程来把当前stage转交给e执行。
  • 如果e为null:
    • 并且前一个stage还没执行完毕。
  • 上面几种情况,最终都会入栈,不管e是否为null,都有必要再尝试一下c.tryFire(SYNC),避免此时前一个stage已经完成的情况。
  • c.tryFire(SYNC)中也会执行类似d.uniApply(this, f, null),而且你会发现两种调用环境,uniApply成员函数的this对象是一样的(当前stage),第一个实参是一样的(前一个stage),第二个实参也是同一个函数式接口对象,只有第三个实参不一样。

UniApply内部类#tryFire

在讲tryFire之前,我们先看看tryFire有几处调用:

  • uniApplyStage中的同步调用,c.tryFire(SYNC)
  • 执行前一个stage的线程,在rund.postComplete()中,会调用tryFire(NESTED)
  • 上面两处,tryFire的this对象都是我们分析过程提到的当前stage。并且,这说明tryFire可能会有多线程的竞争问题,来看看tryFire是怎么解决的。
    • 多线程竞争,比如当前线程入栈后,执行前一个stage的线程刚完事,正要触发后续stage(rund.postComplete()中)。
	//src代表前一个stage, dep代表当前stage。  UniApply对象将两个stage组合在一起了。
    static final class UniApply<T,V> extends UniCompletion<T,V> {
        Function<? super T,? extends V> fn;
        UniApply(Executor executor, CompletableFuture<V> dep,
                 CompletableFuture<T> src,
                 Function<? super T,? extends V> fn) {
            super(executor, dep, src); this.fn = fn;
        }
        final CompletableFuture<V> tryFire(int mode) {
            CompletableFuture<V> d; CompletableFuture<T> a;
            //1. 如果dep为null,说明当前stage已经被执行过了
            //2. 如果uniApply返回false,说明当前线程无法执行当前stage。返回false有可能是因为
            //     1. 前一个stage没执行完呢
            //     2. 前一个stage执行完了,但当前stage已经被别的线程执行了。如果提供了线程池,那么肯定属于被别的线程执行了。   
            if ((d = dep) == null ||
                !d.uniApply(a = src, fn, mode > 0 ? null : this))
                return null;
            //执行到这里,说明dep不为null,而且uniApply返回true,说明当前线程执行了当前stage
            dep = null; src = null; fn = null;
            return d.postFire(a, mode);
        }
    }

看来这个竞争关系体现到了d.uniApply(a = src, fn, mode > 0 ? null : this),分析上面两种情况,发现mode > 0 ? null : this必然不成立,而this指的是UniApply对象(在CompletableFuture#uniApplyStage中创建的)。现在好了,上面两种情况第三个实参都是同一个UniApply对象(竞争处理的关键,之后讲),即两种情况对CompletableFuture#uniApply调用情况一模一样,竞争在这里面处理。

注意,d = dep) == null已经起到了一定的防止竞争的作用,让线程提前返回。但也有可能起不到作用,因为两个线程刚好都执行到了d.uniApply(a = src, fn, mode > 0 ? null : this)

CompletableFuture#uniApply

这个函数的逻辑之前讲过简单的一版,即当第三个实参为null时的情况。所以这里,重点关注第三个实参不为null的情况。

	//this永远是当前stage,a参数永远是前一个stage
    final <S> boolean uniApply(CompletableFuture<S> a,
                               Function<? super S,? extends T> f,
                               UniApply<S,T> c) {
        Object r; Throwable x;
        //前后两个条件只是优雅的避免空指针异常,实际不可能发生。
        //如果 前一个stage的result为null,说明前一个stage还没执行完毕
        if (a == null || (r = a.result) == null || f == null)
            return false;
        //执行到这里,说明前一个stage执行完毕

		//如果this即当前stage的result不为null,说当前stage还没执行。
        tryComplete: if (result == null) {  //一定程度防止了竞争
        	//如果前一个stage的执行结果为null或者抛出异常
            if (r instanceof AltResult) {
                if ((x = ((AltResult)r).ex) != null) {
                	//如果前一个stage抛出异常,那么直接让当前stage的执行结果也为这个异常,都不用执行Function了
                    completeThrowable(x, r);
                    break tryComplete;
                }
                //如果前一个stage的执行结果为null
                r = null;//那么让r变成null
            }
            try {
            	//1. c为null,这说明c还没有入栈,没有线程竞争。直接执行当前stage即f.apply(s)
            	//2. c不为null,这说明c已经入栈了,有线程竞争执行当前stage。
                if (c != null && !c.claim())
                	//claim返回了false,说明当前线程不允许执行当前stage,直接返回
                    return false;
                    //claim返回了true,说明当前线程允许接下来执行当前stage
                @SuppressWarnings("unchecked") S s = (S) r;
                completeValue(f.apply(s));
            } catch (Throwable ex) {
                completeThrowable(ex);
            }
        }
        //如果this即当前stage的result不为null,说当前stage已经执行完毕,那么直接返回true
        return true;
    }
  • if ((x = ((AltResult)r).ex) != null)判断中,如果发现前一个stage执行完是因为抛出了异常,那么当前stage也不能正常执行了,直接把这个异常赋值给当前stage的result成员。
    • break tryComplete后,uniApply函数会马上返回true,然后回到tryFire函数,紧接着马上执行dep = null(这代表当前stage已经执行完毕)。这样可以使得某种特殊时序下,执行前一个stage的线程不会通过tryFire函数中的(d = dep) == null的检查,进而直接返回null不去执行d.postFire(a, mode);。总之,这是为了避免对同一个CompletableFuture对象调用它的成员函数postComplete
    • 但貌似上面说的这种防止线程竞争的手段不是完全有效的,因为“紧接着马上执行dep = null”不是原子性,两个线程的执行顺序也有可能穿插执行,有可能当前线程还没来得及“执行dep = null”,执行前一个stage的线程就开始执行tryFire函数了。
  • if (c != null && !c.claim())是用来保护函数式接口只被执行一次的。claim函数返回true代表函数式接口接下来可以被当前线程同步执行。
        final boolean claim() {
            Executor e = executor;
            //如果该Completion包含有Executor,那么此函数每次都会返回false。
            //  CAS保证函数式接口只被提交一次给Executor
            //如果该Completion没有Executor,那么此函数第一次返回true,之后每次返回false。
            //  CAS保证函数式接口只被同步执行一次
            if (compareAndSetForkJoinTaskTag((short)0, (short)1)) {
                if (e == null)
                    return true;
                executor = null; // disable
                e.execute(this);
            }
            return false;
        }
  • 我们上一章提到的竞争关系,全部在claim()函数中处理掉了。经过CAS的保护,这个函数式接口能够保证只被执行一次(可能是同步执行、或者提交给Executor异步执行)。
  • CompletableFuture#uniApply的返回值含义:
    • 返回false,代表前一个stage还没完成。也可代表,当前stage的函数式接口已经被别的线程执行了。
    • 返回true,代表前一个stage已经完成,并且当前stage的函数式接口被当前线程执行了。
    • 函数返回后,回到tryFire,将执行d.postFire(a, mode),因为执行完毕了当前stage的函数式接口,当前线程就得处理当前stage的后续任务。

梳理到这里,例子中的驱动关系就更加明白了:
JUC框架 CompletableFuture源码解析 JDK8_第2张图片

谁执行了当前stage,谁负责处理后续stage

tryFire函数中,如果d.uniApply(a = src, fn, mode > 0 ? null : this)返回了true,说明当前线程执行了当前stage的函数式接口(这说明没有提供线程池,当前线程同步执行了stage),自然接下来会去处理后续stage(d.postFire(a, mode))。

在提供了线程池的情况下,且前一个stage没有抛出异常正常执行完的情况下,tryFire函数中的d.uniApply(a = src, fn, mode > 0 ? null : this)必然会返回false,因为它把uniApply对象提交给了线程池来执行。当前线程将不会去处理后续stage了(d.postFire(a, mode))。

提交给线程池后,因为uniApply对象也是一个Runnable对象,它的run函数为:

        public final void run()                { tryFire(ASYNC); }

线程池将提供一个线程来执行tryFire(ASYNC),之后d.uniApply(a = src, fn, mode > 0 ? null : this)将会直接执行当前stage的函数式接口,返回true后,再去处理后续stage(d.postFire(a, mode))。

Async任务的执行过程

  1. 执行前一个stage的线程来执行UniApply内部类对象的tryFire(NESTED)
  2. 接着执行当前stage.uniApply()
  3. 执行UniApply内部类对象的claim方法。
  4. 由于提供了线程池,claim会把任务提交给线程池(e.execute(this)),把保存的线程池清理掉(executor = null),然后返回false。执行前一个stage的线程接着层层返回,最终tryFire(NESTED)返回null。
  5. 线程池选择一个线程,开始对同一个UniApply内部类对象执行tryFire(ASYNC)
  6. 接着执行当前stage.uniApply(a = src, fn, ASYNC > 0 ? null : this),第三个实参肯定为null。ASYNC为1.
  7. if (c != null && !c.claim())不会执行,因为第三个参数为null。
  8. 这个线程以“同步”的方式来执行了任务。但它对于执行前一个stage的线程来说是异步的。

CompletableFuture#postComplete 树形结构

    public static void test() {
        CompletableFuture<String> a = CompletableFuture.supplyAsync(() -> {
            String supplyAsyncResult = " "+Thread.currentThread().getName()+" Hello world! ";
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(supplyAsyncResult);
            return supplyAsyncResult;
        });


        a.thenApplyAsync(r -> {  //添加后续任务
            String thenApplyResult = Thread.currentThread().getName()+r + " thenApply1! ";
            System.out.println(thenApplyResult);
            return thenApplyResult;
        });

        a.thenApplyAsync(r -> {  //添加后续任务
            String thenApplyResult = Thread.currentThread().getName()+r + " thenApply2! ";
            System.out.println(thenApplyResult);
            return thenApplyResult;
        });

        try {
            System.out.println(a.get() + " finish!");
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
/*output:
 ForkJoinPool.commonPool-worker-9 Hello world! 
ForkJoinPool.commonPool-worker-9 ForkJoinPool.commonPool-worker-9 Hello world!  thenApply2! 
ForkJoinPool.commonPool-worker-9 ForkJoinPool.commonPool-worker-9 Hello world!  thenApply1! 
 ForkJoinPool.commonPool-worker-9 Hello world!  finish!
*/

虽然上面讲到的例子为链式编程,但其实也可能变成树形结构,这也将解释为什么需要一个Completion stack作为栈来存储后续任务。从上例可见,在a对象之后,我们添加了两个任务,这两个任务是并列。因为是栈结构,所以最后的任务反而先执行。
JUC框架 CompletableFuture源码解析 JDK8_第3张图片
我们以树形结构来体现上例,由于两个后续任务是并列的,它们都被添加到a对象的stack中等待被执行。
JUC框架 CompletableFuture源码解析 JDK8_第4张图片

当前stage被完成后,postComplete函数就会被调用。比如AsyncSupply内部类的run函数的最后;UniApply内部类的tryFire函数的最后。

//this对象总是那个 刚完成的当前stage
final void postComplete() {
    CompletableFuture<?> f = this; Completion h;//f初始指向当前stage对象,也就是图中的a节点
    //注意,遍历过程中,f可能不再指向this,而是树形结构下面层次的节点

    //1. 如果当前f的stack不为null,那么短路后面
    //2. 如果当前f的stack为null,且f是this(f != this不成立),说明整个树形结构已经被遍历完毕,退出循环
    //3. 如果当前f的stack为null,且f不是this,那么让f恢复为this,再查看this的stack是否为null
    //      1. 如果
    while ((h = f.stack) != null ||
           (f != this && (h = (f = this).stack) != null)) {
        CompletableFuture<?> d; Completion t;
        if (f.casStack(h, t = h.next)) {  //使得f的栈顶指针下移
            if (t != null) {              //如果h是有后继的
                if (f != this) {          //如果f不是树形结构的root,就把f的后续任务压入root(this对象)的栈,直到全部压入
                    pushStack(h);
                    continue;
                }
                h.next = null;            //如果h是有后继的,需要断开h的next指针
            }
            //Completion执行tryFire(NESTED)后,有两种情况:
            //1. 以上图为例,可能会返回Completion对象的当前stage。因为当前线程在tryFire中执行了h的当前stage
            //   所以f会更新为树形结构中的非root的节点
            //2. 也有可能会返回null。因为当前线程在tryFire中没有执行h的当前stage,这说明h的当前stage的stack不需要当前线程来处理了。
            //   所以要把f恢复为this
            f = (d = h.tryFire(NESTED)) == null ? this : d;
        }
    }
}

如下图所示,对后续任务的处理可能会变成树形结构的遍历,一般做法是递归去处理,但这样可能会造成很深的递归。而对tryFire的调用可能是这种过程:postComplete -> tryFire -> postFire -> postComplete,这样是会造成递归调用的。

但是现在,该函数通过tryFire(NESTED)的特殊处理,并且在遇到第二层次以下的后续任务时,先把后续任务放到树形结构中的第二层次,再来执行,从而解决了递归调用的问题。tryFire(NESTED)的特殊处理是指,当参数为NESTED时,只处理这个Completion对象的当前stage,不处理当前stage的后续stage。
JUC框架 CompletableFuture源码解析 JDK8_第5张图片
以上图为例,蓝色节点代表局部变量f指向的节点。当a节点的stage完成,但还没执行postComplete时,整个树形结构如上图。
JUC框架 CompletableFuture源码解析 JDK8_第6张图片
执行完第一次循环后,如上图,此时刚完成了b节点的stage。
JUC框架 CompletableFuture源码解析 JDK8_第7张图片
下一次循环中,由于局部变量f并没有指向this,所以把f的后续任务弄到第二层次去。上面的过程连续做几次,直到把f的后续任务全弄没。
JUC框架 CompletableFuture源码解析 JDK8_第8张图片

把局部变量f的后续任务全弄没后,如上图。
JUC框架 CompletableFuture源码解析 JDK8_第9张图片
下一次循环的检查条件将更新f为根节点,接下来就像第一次调用postComplete时,再依次处理第二层次的各个后续任务。

postComplete可以被并发调用

这种并发调用类似于ConcurrentHashMap的transfer函数,在transfer函数中,各个线程领取各自的连续哈希桶区间作为任务,领取成功后将transferIndex前移。

postComplete也能安全的并发调用,因为每个线程领取任务也是通过f.casStack(h, t = h.next)成功出栈节点来完成的,因为是CAS操作,所以两个线程不可能出栈同一个节点。

但是,这种并发调用其实很难发生,我这里只想到了一种case。以树形结构为例,假设现在有根节点线程(supplyAsync产生的),树形结构的第二层次已经有很多节点了(已执行根节点.thenApply()),现在还有一个正在执行的同步线程(正在执行根节点.thenApply())。

    private <V> CompletableFuture<V> uniApplyStage(
        Executor e, Function<? super T,? extends V> f) {
        if (f == null) throw new NullPointerException();
        CompletableFuture<V> d =  new CompletableFuture<V>();
        if (e != null || !d.uniApply(this, f, null)) {
            UniApply<T,V> c = new UniApply<T,V>(e, d, this, f);
            push(c);
            //此时,根节点线程突然执行过程中抛出了异常。假设此时根节点线程已经执行了run函数的d.completeThrowable(ex)
            //但还执行到d.postComplete()
            c.tryFire(SYNC);
        }
        return d;
    }
		//假设根节点线程和同步线程一起执行到tryFire。注意,tryFire的this对象是同一个
        final CompletableFuture<V> tryFire(int mode) {
            CompletableFuture<V> d; CompletableFuture<T> a;
            if ((d = dep) == null ||
                !d.uniApply(a = src, fn, mode > 0 ? null : this))//两个线程一起执行到这里
                return null;
            dep = null; src = null; fn = null;
            return d.postFire(a, mode);//两个线程一起执行到这里
        }

    final <S> boolean uniApply(CompletableFuture<S> a,
                               Function<? super S,? extends T> f,
                               UniApply<S,T> c) {
        Object r; Throwable x;
        if (a == null || (r = a.result) == null || f == null)
            return false;
        tryComplete: if (result == null) {
            if (r instanceof AltResult) {
                if ((x = ((AltResult)r).ex) != null) {
                    completeThrowable(x, r);//两个线程一起执行到这里
                    break tryComplete;
                }
                r = null;
            }
            try {
                if (c != null && !c.claim())
                    return false;
                @SuppressWarnings("unchecked") S s = (S) r;
                completeValue(f.apply(s));
            } catch (Throwable ex) {
                completeThrowable(ex);
            }
        }
        return true;//两个线程一起执行到这里
    }

postFire中有所不同。

    final CompletableFuture<T> postFire(CompletableFuture<?> a, int mode) {
        if (a != null && a.stack != null) {
            if (mode < 0 || a.result == null)
                a.cleanStack();
            else
                a.postComplete();
        }
        if (result != null && stack != null) {
            if (mode < 0)
                return this;
            else
                postComplete();
        }
        return null;
    }
  • 根节点线程,由于参数mode是NESTED,不会在这里执行任何postComplete,但mode是NESTED说明是上层函数postComplete调用进来的,然后postFire返回 -> tryFire返回 -> postComplete继续执行,继续执行的postComplete的this对象是根节点。
  • 同步线程,由于参数mode是SYNC,并且参数a的result不为null(根节点线程执行自身stage时抛出了异常嘛),所以会执行a.postComplete(),这里参数a是this对象的前一个stage,也就是例子中的根节点。
  • 综上,对于同一个CompletableFuture对象的postComplete可能被并发调用。

CompletableFuture#postFire

在前面的分析可知,UniApply#tryFire成功执行了这个UniApply对象的当前stage后,将会调用当前stage的postFire

    final CompletableFuture<T> postFire(CompletableFuture<?> a, int mode) {
    	//前一个stage的后续任务还没做完
        if (a != null && a.stack != null) {
            //1. mode为NESTED。说明就是postComplete调用过来的,那么只清理一下栈中无效节点即可。
            //2. mode为SYNC或ASYNC,但前一个stage还没执行完。不知道何时发生,因为调用postFire的前提就是前一个stage已经执行完
            if (mode < 0 || a.result == null)
                a.cleanStack();
            //3. mode为SYNC或ASYNC,但前一个stage已经执行完了。特殊时序可能发生的,那么帮忙完成前一个stage的的后续任务
            else
                a.postComplete();
        }
        //当前stage的后续任务还没做完
        if (result != null && stack != null) {
            if (mode < 0)//mode为NESTED。说明就是postComplete调用过来的.
                return this;
            else         //mode为SYNC或ASYNC,那么调用postComplete
                postComplete();
        }
        return null;
    }

CompletableFuture#cleanStack

		//UniCompletion内部类
        final boolean isLive() { return dep != null; }

我们知道一个Completion对象的当前stage如果已经完成,那么它的dep成员肯定为null。

    final void cleanStack() {
    	//遍历过程中,可能的结构为p -> q -> s,  q为当前遍历节点
        for (Completion p = null, q = stack; q != null;) {
            Completion s = q.next;
            if (q.isLive()) {  //如果q是有效节点,更新q,用p保存旧q
                p = q;
                q = s;
            }
            //如果q不是有效节点
            else if (p == null) {  //如果q是栈顶,那么移动栈顶
                casStack(q, s);
                q = stack;
            }
            else {  //如果q不是栈顶,那么使得p -> q -> s变成p -> s,达到移除q的目的
                p.next = s;
                //但这种移除成功的前提是q是一个有效节点
                if (p.isLive()) //如果是有效的移除
                    q = s;  
                else {   //如果不是有效的移除
                    p = null;  // 需要从新开始整个循环
                    q = stack;
                }
            }
        }
    }

整个移除过程符合Treiber stack的要求,具体讲解请看FutureTask的removeWaiter函数实现–带图讲解,二者实现完全一样。

thenApplyAsync另起线程

我们知道thenApplyAsync有两个重载版本,一个的线程池是默认的ForkJoinPool.commonPool(),另一个的线程池则是调用者提供的线程池。

一般的链式操作为supplyAsync() ⇒ thenApplyAsync(),而Async提供的语义是异步执行,与当前线程肯定不是同一个线程。当前线程就是调用supplyAsyncthenApplyAsync的线程。

但注意,supplyAsyncthenApplyAsync这二者执行线程并不一定是同一个线程,这取决于线程池的选择。在claim函数中可见,当发现提供有线程池时,当前线程只是将任务提交给了线程池后(e.execute(this))就马上返回了。

本章开始的例子,会发现这二者执行线程总是同一个。

其他方法与thenApply的区别

thenApply是一个CompletableFuture的成员方法,它依赖于前置stage的执行完成。而这样的方法还有很多。

普通方法(依赖的CompletableFuture个数为1个或者2个)

0. thenApply(thenApplyAsync)

thenApply需要一个输入,有输出。

1. thenAccept(thenAcceptAsync)

两个函数都会调用到CompletableFuture#uniAcceptStage,看起来只是函数字眼变了,另外是靠UniAccept内部类来驱动的。

    private CompletableFuture<Void> uniAcceptStage(Executor e,
                                                   Consumer<? super T> f) {
        if (f == null) throw new NullPointerException();
        CompletableFuture<Void> d = new CompletableFuture<Void>();
        if (e != null || !d.uniAccept(this, f, null)) {
            UniAccept<T> c = new UniAccept<T>(e, d, this, f);
            push(c);
            c.tryFire(SYNC);
        }
        return d;
    }

观察整个过程你会发现,只有CompletableFuture#uniAccept发生了实质的变化。

    final <S> boolean uniAccept(CompletableFuture<S> a,
                                Consumer<? super S> f, UniAccept<S> c) {
			...
            try {
                if (c != null && !c.claim())
                    return false;
                @SuppressWarnings("unchecked") S s = (S) r;
                f.accept(s);//执行完函数式接口后
                completeNull();//当前stage的result被赋值为null的包装对象,代表当前stage结束
            } catch (Throwable ex) {
                completeThrowable(ex);
            }
        	...
    }

看来thenAccept方法需要一个输入,但没有输出。

2. thenRun(thenRunAsync)

直接看CompletableFuture#uniRun

    final boolean uniRun(CompletableFuture<?> a, Runnable f, UniRun<?> c) {
        Object r; Throwable x;
        if (a == null || (r = a.result) == null || f == null)
            return false;
        if (result == null) {
            if (r instanceof AltResult && (x = ((AltResult)r).ex) != null)
                completeThrowable(x, r);
            else
                try {
                    if (c != null && !c.claim())
                        return false;
                    f.run();  //运行函数式接口时,连输入都不需要了
                    completeNull();
                } catch (Throwable ex) {
                    completeThrowable(ex);
                }
        }
        return true;
    }

看来thenRun方法不需要输入,也没有输出。但也得等到依赖的stage完成后,才能执行当前stage。

2.5

你可能会想,为什么没有一个“不需要输入,但需要输出”的版本,其实这就是thenApply啦,因为你可以让你的Supplier函数式接口直接忽略掉参数。

简单总结一下:

需要输入 是否输出
applyXXX
acceptXXX ×
runXXX × ×

3. thenCombine(thenCombineAsync)

	//this对象和o参数,是两个输入
    private <U,V> CompletableFuture<V> biApplyStage(
        Executor e, CompletionStage<U> o,
        BiFunction<? super T,? super U,? extends V> f) {
        CompletableFuture<U> b;
        if (f == null || (b = o.toCompletableFuture()) == null)
            throw new NullPointerException();
        CompletableFuture<V> d = new CompletableFuture<V>();
        if (e != null || !d.biApply(this, b, f, null)) {
            BiApply<T,U,V> c = new BiApply<T,U,V>(e, d, this, b, f);
            bipush(b, c);//入栈的时候,把Completion对象入栈到 两个输入的stack中
            c.tryFire(SYNC);
        }
        return d;
    }
	//this对象和b参数,是两个输入
    final void bipush(CompletableFuture<?> b, BiCompletion<?,?,?> c) {
        if (c != null) {
            Object r;
            while ((r = result) == null && !tryPushStack(c))//先入栈到this对象里去
                lazySetNext(c, null); // clear on failure
            if (b != null && b != this && b.result == null) {
                Completion q = (r != null) ? c : new CoCompletion(c);//CoCompletion只起到包装的作用,实际上所有实现都依赖于c
                while (b.result == null && !b.tryPushStack(q))//再入栈到b参数里去
                    lazySetNext(q, null); // clear on failure
            }
        }
    }

最大的区别在于,Completion对象对两个输入CompletableFuture,都要做入栈动作。

再看会tryFire调用的CompletableFuture#biApply

    final <R,S> boolean biApply(CompletableFuture<R> a,  //需要有两个输入,所以有两个CompletableFuture参数
                                CompletableFuture<S> b,
                                BiFunction<? super R,? super S,? extends T> f,
                                BiApply<R,S,T> c) {
        Object r, s; Throwable x;
        if (a == null || (r = a.result) == null ||       //需要检查两个CompletableFuture是否已经都已经完成了
            b == null || (s = b.result) == null || f == null)
            return false;
        tryComplete: if (result == null) {
            if (r instanceof AltResult) {
                if ((x = ((AltResult)r).ex) != null) {
                    completeThrowable(x, r);
                    break tryComplete;
                }
                r = null;
            }
            if (s instanceof AltResult) {
                if ((x = ((AltResult)s).ex) != null) {
                    completeThrowable(x, s);
                    break tryComplete;
                }
                s = null;
            }
            try {
                if (c != null && !c.claim())
                    return false;
                @SuppressWarnings("unchecked") R rr = (R) r;
                @SuppressWarnings("unchecked") S ss = (S) s;
                completeValue(f.apply(rr, ss));  //函数式接口接受了两个参数
            } catch (Throwable ex) {
                completeThrowable(ex);
            }
        }
        return true;
    }

看来thenCombine方法需要两个输入,有输出。

4. thenAcceptBoth(thenAcceptBothAsync)

类似于thenCombine方法。thenAcceptBoth方法需要两个输入,但没有输出。

5. runAfterBoth(runAfterBothAsync)

类似于thenCombine方法。runAfterBoth方法不需要输入,也没有输出。但执行当前stage,需要等到输入的两个stage完成后才能执行。

6. applyToEither(applyToEitherAsync)

applyToEither与thenCombine类似,但thenCombine对两个输入stage的关系是&&,而applyToEither对两个输入stage的关系是||。即只要两个输入参数有一个执行完毕了,就可以执行当前stage的函数式接口,并且 两个输入参数谁先执行完毕,就使用谁的结果作为函数式接口的输入。

    final <R,S extends R> boolean orApply(CompletableFuture<R> a,
                                          CompletableFuture<S> b,
                                          Function<? super R, ? extends T> f,
                                          OrApply<R,S,T> c) {
        ...
        if (a == null || b == null ||
            ((r = a.result) == null && (r = b.result) == null) || f == null)//a的结果优先使用
            return false;
        ...
    }

注意,执行a.applyToEither(b, fn)时,如果两个输入stage都已经结束了,那么优先使用a这个输入stage的结果。

acceptEither和runAfterEither的分析类似,不再赘述。

7. handle(handleAsync)

虽然handle的参数为BiFunction类型,但不要以为它真正会接受到两个有效参数。两个参数一个代表前一个stage正常执行完,一个代表前一个stage抛出异常,所以两个参数只有一个是非null的。

handle与前面函数的最大区别在于,它可以处理前一个stage抛出的异常。

final <S> boolean uniHandle(CompletableFuture<S> a,
                                BiFunction<? super S, Throwable, ? extends T> f,
                                UniHandle<S,T> c) {
    ...
            try {
                if (c != null && !c.claim())
                    return false;
                if (r instanceof AltResult) {// r可能包装了null,也可能包装了异常
                    x = ((AltResult)r).ex;   // x可能为null,也可能不是null
                    s = null;                // s肯定是null
                } else {                     // 前一个stage正常执行完成,且不是null的执行结果
                    x = null;  //x肯定是null
                    @SuppressWarnings("unchecked") S ss = (S) r;
                    s = ss;    // s肯定不是null
                }
                completeValue(f.apply(s, x));
            } catch (Throwable ex) {
                completeThrowable(ex);
            }
        }
    ...
    }

所以,我们可以通过实参来判断到底是哪种情况:

s x 情况
s非null x为null 正常执行完,且执行结果非null
s为null x为null 正常执行完,且执行结果为null
s为null x非null 执行过程中,抛出了异常

简单的说,可以通过x是否为null来判断是否抛出了异常。但不可能两个参数都是非null的。

handle需要输入,有输出。

whenComplete与handle类似,只是whenComplete没有输出。

8. exceptionally

首先注意这个函数没有Async版本,在初始化内部类UniExceptionally对象时,给的Executor也是为null。

这个函数只处理前一个stage发现异常的情况,如果前一个stage正常执行完毕,那么直接将执行结果赋值给当前stage。

    final boolean uniExceptionally(CompletableFuture<T> a,
                                   Function<? super Throwable, ? extends T> f,
                                   UniExceptionally<T> c) {
       ...                            
            try {
                if (r instanceof AltResult && (x = ((AltResult)r).ex) != null) {  //如果前一个stage抛出了异常
                    if (c != null && !c.claim())
                        return false;
                    completeValue(f.apply(x)); // 处理异常
                } else   //如果前一个stage正常执行完
                    internalComplete(r); // 直接传递结果
            } catch (Throwable ex) {
                completeThrowable(ex);
            }
        }
       ... 
    }

9. thenCompose(thenComposeAsync)

这个方法有点特殊,你可以简单理解thenCompose为thenApplyAsync,总之,本文不准备讲解这俩函数。

allOf

前面提供了依赖CompletableFuture个数为1或2的方法,但现在总不能给3,4…的版本都写一遍吧,所以提供了一种allOf方法,但它只能保证时机,并不能利用all任务们的执行结果。

allOf使用了一种递归分治的方法,虽然任务可能有很多个,但最终它们都能拆分为1个或者2个。从最底层两两合并,弄成一个树形结构,最终这个树形结构的根节点就是我们想要的CompletableFuture,它们代表了所有任务都完成的时机。

    public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) {
        return andTree(cfs, 0, cfs.length - 1);
    }

CompletableFuture的返回类型可知,allOf方法返回的CompletableFuture对象只是代表一种时机,这个时机就是cfs里面的CompletableFuture都已经执行完毕。当我们在allOf方法返回的CompletableFuture对象上添加后续任务时,能保证后续任务是这个特殊时机后才会触发的。

这种CompletableFuture对象它没有真正的task要执行。

    static CompletableFuture<Void> andTree(CompletableFuture<?>[] cfs,
                                           int lo, int hi) {
        CompletableFuture<Void> d = new CompletableFuture<Void>();
        if (lo > hi) // empty,不会执行到
            d.result = NIL;
        //执行到这里lo <= hi
        else {
            CompletableFuture<?> a, b;
            int mid = (lo + hi) >>> 1;
            //下面将对a和b进行赋值
            //如果lo和hi相差1或者二者相等的话,mid会等于lo,则让a直接为lo索引处元素
            //如果是其他情况,则递归调用
            if ((a = (lo == mid ? cfs[lo] :
                      andTree(cfs, lo, mid))) == null ||
                //如果lo等于hi,那么让b直接为a
                //如果lo+1等于hi,那么让b为hi索引处元素
                //如果是其他情况,则递归调用
                (b = (lo == hi ? a : (hi == mid+1) ? cfs[hi] :
                      andTree(cfs, mid+1, hi)))  == null)
                throw new NullPointerException();
            //无论前面有没有递归代用,执行到这里,a和b都已经赋值好了
            if (!d.biRelay(a, b)) {//直接先尝试看看,a和b是否都已经执行完,返回false代表没有执行完
                BiRelay<?,?> c = new BiRelay<>(d, a, b);//这个对象主要为了保存两个src和一个dep
                a.bipush(b, c);//这里可能会将c先后入栈a和b
                c.tryFire(SYNC);//入栈后再尝试一次,实际也是调用d.biRelay(a, b)
            }
        }
        return d;
    }

在入栈前,我们先直接检查a和b是否都已经执行完毕。这个函数很重要,在tryFire中也会调用到它:

	//this为dep对象,a和b为两个srcUI对象
    boolean biRelay(CompletableFuture<?> a, CompletableFuture<?> b) {
        Object r, s; Throwable x;
        if (a == null || (r = a.result) == null ||
            b == null || (s = b.result) == null)
            return false;
        //如果两个src对象都已经执行完毕,则肯定返回true
        if (result == null) {
        	//this对象的result对需要变成非null,原则是只要有一个src抛出了异常,result就设置异常,否则设置为null的包装对象
            if (r instanceof AltResult && (x = ((AltResult)r).ex) != null)
                completeThrowable(x, r);
            else if (s instanceof AltResult && (x = ((AltResult)s).ex) != null)
                completeThrowable(x, s);
            else
                completeNull();
        }
        return true;
    }

如果第一次尝试失败,则马上入栈。

来看一下a.bipush(b, c)是实际是怎么入栈的。

    final void bipush(CompletableFuture<?> b, BiCompletion<?,?,?> c) {
        if (c != null) {
            Object r;
            while ((r = result) == null && !tryPushStack(c))//如果this对象还没完成,那么将c入栈到this
                lazySetNext(c, null); // 如果入队失败,c的next指针清空,避免内存泄漏
            //执行到这里,要么this已经完成,要么已经将c入栈this。总之,对this对象的完成时机已经有了保证
            
            //有可能b也是this对象,这种情况不需要做什么。
            if (b != null && b != this && b.result == null) {//如果b已经完成,那么不需要做入栈操作
                //如果this已经完成,那么赋值c。因为只需要等待一个了
                //如果this还没完成,那么赋值CoCompletion(c)
                Completion q = (r != null) ? c : new CoCompletion(c);
                while (b.result == null && !b.tryPushStack(q))
                    lazySetNext(q, null); // clear on failure
            }
        }
    }

看起来给第一个src对象入栈的是c,但第二个src对象入栈的是new CoCompletion(c)

入栈的对象是一种Completion对象,当它的前置stage执行完毕时,将调用它的tryFire方法来完成它自身的stage。来看下BiRelay即变量ctryFire方法:

    static final class BiRelay<T,U> extends BiCompletion<T,U,Void> { // for And
        BiRelay(CompletableFuture<Void> dep,
                CompletableFuture<T> src,
                CompletableFuture<U> snd) {
            super(null, dep, src, snd);
        }
        final CompletableFuture<Void> tryFire(int mode) {
            CompletableFuture<Void> d;
            CompletableFuture<T> a;
            CompletableFuture<U> b;
            if ((d = dep) == null || !d.biRelay(a = src, b = snd))//实际也是调用d.biRelay,来检查两个前置stage
                return null;//如果两个没有都完成,则返回null
            //如果两个都完成了
            src = null; snd = null; dep = null;
            return d.postFire(a, b, mode);//继续执行后续任务
        }
    }

来看看CoCompletion,实际上它只是包装一下BiRelay

    static final class CoCompletion extends Completion {
        BiCompletion<?,?,?> base;
        CoCompletion(BiCompletion<?,?,?> base) { this.base = base; }
        final CompletableFuture<?> tryFire(int mode) {
            BiCompletion<?,?,?> c; CompletableFuture<?> d;
            if ((c = base) == null || (d = c.tryFire(mode)) == null)//实际上也是调用base的tryFire
                return null;
            base = null; // detach
            return d;
        }
        final boolean isLive() {
            BiCompletion<?,?,?> c;
            return (c = base) != null && c.dep != null;
        }
    }

总之,当BiRelay对象和CoCompletion对象都分别入栈两个src对象后,先完成的src对象在执行后续任务(可能是BiRelay对象或CoCompletion对象)的tryFire时会返回null,因为还差一方。后完成的src对象在执行后续任务的tryFire时,才会执行成功。

JUC框架 CompletableFuture源码解析 JDK8_第10张图片
假如allOf给了五个依赖的stage,那么它的执行过程如上图。没有字母的节点都是属于“虚拟stage”,它们没有真正的函数式接口要执行。而根节点则反映了所有依赖的stage都完成了的时机。通过分治的手段,创建多个虚拟stage,最终得到了我们想要的那个根节点,因为根节点代表allOf这个时机。

anyOf

anyOfallOf类似,我们直接看tryFire会调用的那个关键方法吧。

//CompletableFuture#orRelay
    final boolean orRelay(CompletableFuture<?> a, CompletableFuture<?> b) {
        Object r;
        if (a == null || b == null ||
            ((r = a.result) == null && (r = b.result) == null))//只要有一个src对象完成了,就不会返回false
            return false;
        if (result == null)
            completeRelay(r);
        return true;
    }

果然只要有一个src对象完成了,orRelay就不会返回false。

    final boolean completeRelay(Object r) {
        return UNSAFE.compareAndSwapObject(this, RESULT, null,
                                           encodeRelay(r));
    }

    static Object encodeRelay(Object r) {
        Throwable x;
        return (((r instanceof AltResult) &&
                 (x = ((AltResult)r).ex) != null &&
                 !(x instanceof CompletionException)) ?
                new AltResult(new CompletionException(x)) : r);
    }

最后设置result成员时,如果发现前置stage是抛出了异常,那么就把这个异常包装成一个CompletionException。如果已经是CompletionException了,则不用包装了。最后设置这个异常。当然,如果前置stage正常执行完,result成员会设置为正常执行的结果。

这和allOfbiRelay的处理略有不同。

get相关方法

get()

对于任何CompletableFuture对象,我们都可以调用get函数来阻塞获得它的输出。对于CompletableFuture对象,调用get函数只是为了检测CompletableFuture对象完成的时机。

    public T get() throws InterruptedException, ExecutionException {
        Object r;
        return reportGet((r = result) == null ? waitingGet(true) : r);//如果this对象已经执行完成,直接reportGet
    }

如果this对象还没执行完成,则调用waitingGet,先自旋,再阻塞。当依赖的stage完成时,再将当前线程唤醒。

    private Object waitingGet(boolean interruptible) {
        Signaller q = null;
        boolean queued = false;
        int spins = -1;
        Object r;
        while ((r = result) == null) {
            if (spins < 0)//开始自旋
                spins = (Runtime.getRuntime().availableProcessors() > 1) ?
                    1 << 8 : 0; // Use brief spin-wait on multiprocessors
            else if (spins > 0) {//每次自旋
                if (ThreadLocalRandom.nextSecondarySeed() >= 0)//不一定每次自旋都会
                    --spins;
            }
            //如果自旋次数为0
            else if (q == null)//但还没有生成Signaller
                q = new Signaller(interruptible, 0L, 0L);
            else if (!queued)//生成了但还没入栈
                queued = tryPushStack(q);
            //已经入栈了
            else if (interruptible && q.interruptControl < 0) {//如果支持中断,且当前线程已经被中断
                q.thread = null;//那么不能再等了,直接返回null
                cleanStack();//帮忙依赖的stage,清理stack
                return null;
            }
            else if (q.thread != null && result == null) {
                try {
                    ForkJoinPool.managedBlock(q);//进行阻塞,这里面是也是循环阻塞的过程
                } catch (InterruptedException ie) {
                    q.interruptControl = -1;
                }
            }
        }
        //执行到这里,说明阻塞已唤醒。可能是正常等到了依赖stage执行完,也可能是被中断了
        if (q != null) {
            q.thread = null;
            if (q.interruptControl < 0) {//被中断了
                if (interruptible)//如果本次调用支持被中断,那么返回null
                    r = null; // report interruption
                else//如果本次调用不支持被中断,那么只是自我中断一下
                    Thread.currentThread().interrupt();
            }
        }
        postComplete();//帮忙处理后续任务
        return r;
    }

主要通过ForkJoinPool.managedBlock(q)进行的阻塞等待。

    public static void managedBlock(ManagedBlocker blocker)
        throws InterruptedException {
        ForkJoinPool p;
        ForkJoinWorkerThread wt;
        Thread t = Thread.currentThread();
        if ((t instanceof ForkJoinWorkerThread) &&
            (p = (wt = (ForkJoinWorkerThread)t).pool) != null) {
			...
        }
        else {//只会执行到这里
            do {} while (!blocker.isReleasable() &&//其实block也是在调用isReleasable
                         !blocker.block());
        }
    }

可以看到整个执行过程是while (!blocker.isReleasable() && !blocker.block())

    static final class Signaller extends Completion
        implements ForkJoinPool.ManagedBlocker {
        long nanos;                    // wait time if timed
        final long deadline;           // non-zero if timed
        volatile int interruptControl; // > 0: 可中断的, < 0: 已经被中断了
        volatile Thread thread;

        Signaller(boolean interruptible, long nanos, long deadline) {
            this.thread = Thread.currentThread();
            this.interruptControl = interruptible ? 1 : 0;  //0代表不支持中断
            this.nanos = nanos;
            this.deadline = deadline;
        }
        final CompletableFuture<?> tryFire(int ignore) {
            Thread w; // no need to atomically claim
            if ((w = thread) != null) {
                thread = null;
                LockSupport.unpark(w);//tryFire唤醒即可
            }
            return null;
        }
        //返回true代表当前线程不需要阻塞了
        public boolean isReleasable() {
            if (thread == null)
                return true;
            if (Thread.interrupted()) {
                int i = interruptControl;
                interruptControl = -1;//只要被中断,不管之前的值是什么,都置为-1
                if (i > 0)//如果支持中断,既然支持中断,那么不需要阻塞了
                    return true;
            }
			//虽然发现中断,但此对象不支持中断,那么也需要阻塞。这意味着会一直等到依赖stage执行完成

            if (deadline != 0L &&
                (nanos <= 0L || (nanos = deadline - System.nanoTime()) <= 0L)) {
                thread = null;
                //如果已经超时
                return true;
            }
			//如果还没有超时,则需要阻塞

            return false;
        }
        public boolean block() {
            if (isReleasable())//如果发现不需要阻塞了,那么直接返回
                return true;
            else if (deadline == 0L)//如果不是超时版本,那么无限阻塞
                LockSupport.park(this);
            else if (nanos > 0L)//如果是超时版本,那么限时阻塞
                LockSupport.parkNanos(this, nanos);
            //唤醒后判断是否需要阻塞
            return isReleasable();
        }
        final boolean isLive() { return thread != null; }
    }
  • waitingGet函数的参数interruptible代表获得执行结果的当前线程,可以因为中断而终止阻塞。
  • get函数返回null有两种情况:
    • 依赖的stage正常执行完,且执行结果为null。
    • 依赖的stage还没执行完,但当前线程被中断了。

join()

    public T get() throws InterruptedException, ExecutionException {
        Object r;
        return reportGet((r = result) == null ? waitingGet(true) : r);
    }

join方法与get的唯一区别是,join不支持中断,所以当前线程唤醒的唯一希望就是依赖的stage执行完毕。

超时get

    public T get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        Object r;
        long nanos = unit.toNanos(timeout);
        return reportGet((r = result) == null ? timedGet(nanos) : r);
    }

调用的是timedGet,整个过程类似。我们只需要知道,超时版本是自带支持中断的功能。

    private Object timedGet(long nanos) throws TimeoutException {
        if (Thread.interrupted())//超时版本自带被中断的功能
            return null;
        if (nanos <= 0L)  //不能给无意义的超时时间
            throw new TimeoutException();
        long d = System.nanoTime() + nanos;
        Signaller q = new Signaller(true, nanos, d == 0L ? 1L : d); // 注意第一个参数为true,因为超时版本必定可以中断
        boolean queued = false;
        Object r;
        // 这里没有像waitingGet一样进行自旋,因为限时中断就相当于自旋了
        while ((r = result) == null) {
            if (!queued)
                queued = tryPushStack(q);
            //1. q已经被中断了 
            //2. q已经超时了
            else if (q.interruptControl < 0 || q.nanos <= 0L) {
                q.thread = null;
                cleanStack();
                if (q.interruptControl < 0)//被中断
                    return null;
                throw new TimeoutException();//已经超时
            }
            else if (q.thread != null && result == null) {
                try {
                    ForkJoinPool.managedBlock(q);
                } catch (InterruptedException ie) {
                    q.interruptControl = -1;
                }
            }
        }
        if (q.interruptControl < 0)//被中断了,就返回null
            r = null;
        q.thread = null;
        postComplete();
        return r;
    }

cancel相关方法

前提为result为null

cancel函数只是将CompletableFuture对象的result进行了赋值,但只有当CompletableFuture对象还没完成时。

    public boolean cancel(boolean mayInterruptIfRunning) {
        boolean cancelled = (result == null) &&//在stage还没有完成时,将this的result赋值为CancellationException的包装对象
            internalComplete(new AltResult(new CancellationException()));//CAS可能失败
		//1. 当前线程修改成功。
		//2. 当前线程没修改成功,但其他线程cancel时修改成功。
		//3. 当前线程没修改成功,是因为当前stage已完成

		//既然此时result肯定不为null了,那么就需要帮忙处理后续
        postComplete();//处理这个stage的后续任务
        return cancelled || isCancelled();
    }

    public boolean isCancelled() {//考虑CAS失败,是因为别的线程调用了cancel,所以这里再检查一次
        Object r;
        return ((r = result) instanceof AltResult) &&
            (((AltResult)r).ex instanceof CancellationException);
    }

对比FutureTask的实现:

  • CompletableFuture并没有对执行当前stage的线程做任何处理。因为CompletableFuture实际上没有和任何线程绑定在一起,所以也没法处理。FutureTask有可能 能终止生产者的执行,并且在生产者已执行的情况下,可以去中断生产者线程。
  • CompletableFuture的cancel多次调用都会返回true。FutureTask的cancel只有一次调用可以返回true。
    public boolean complete(T value) {
        boolean triggered = completeValue(value);
        postComplete();
        return triggered;
    }

    final boolean completeValue(T t) {
        return UNSAFE.compareAndSwapObject(this, RESULT, null,
                                           (t == null) ? NIL : t);
    }

complete一样有这个前提条件,只是赋值目标为null的包装对象。

    public boolean completeExceptionally(Throwable ex) {
        if (ex == null) throw new NullPointerException();
        boolean triggered = internalComplete(new AltResult(ex));
        postComplete();
        return triggered;
    }

    final boolean internalComplete(Object r) { // CAS from null to r
        return UNSAFE.compareAndSwapObject(this, RESULT, null, r);
    }

complete一样有这个前提条件,只是赋值目标为异常对象。

没有前提

强行赋值,没有CAS操作。不管stage有没有执行完毕。

    public void obtrudeValue(T value) {
        result = (value == null) ? NIL : value;
        postComplete();
    }

    public void obtrudeException(Throwable ex) {
        if (ex == null) throw new NullPointerException();
        result = new AltResult(ex);
        postComplete();
    }

总结

  • CompletableFuture通过异步回调来处理执行结果,解决了FutureTask对执行结果获取的痛点。
  • 用户只能接触到CompletableFuture对象,靠Completion对象来驱动CompletableFuture对象。注意,Completion是实现了Runnable的。
  • 一般情况下,我们使用CompletableFuture的链式编程。但是通过组合方式,也可以把任务的排列由一个链变成一个树形结构。参照CompletableFuture#postComplete的实现。

你可能感兴趣的:(Java,Future,异步回调,java,JUC,多线程)