JUC框架 CompletableFuture源码解析 JDK8

本文深入解析了CompletableFuture的实现原理,包括其异步回调机制、内部数据结构、链式编程和树形结构处理,以及与FutureTask的区别。探讨了CompletableFuture如何通过Completion对象驱动执行,异步任务的执行流程,和各种异步方法的使用场景。

前言

我们知道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<Void>,但也仅仅是为了套上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<U>(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<U>(d, f)),run函数就会被另一个线程执行。当task被异步执行完毕后,会调用completeValuecompleteThrowable来为result成员赋值。
在这里插入图片描述
上图体现了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<V> d = new CompletableFuture<V>()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
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值