CompletableFuture 的 allOf 方法底层原理是什么

CompletableFuture 讲解系列



前言

CompletableFuture估计多数程序员都了解那么一丢丢,
网上介绍 API 的文章一大把,至于原理么,呵呵,不讲。

能把源码讲清楚的文章,约等于 0,甚少我一直没找到。
Doug Lea 的代码,可谓鬼斧神工,精妙绝伦,难懂!

今儿在这儿,我壮胆写篇文章,剖析下源码,探究下原理。
各位路过的大佬儿,还请多多指教。若有谬误,烦请指正。


一、CompletableFuture 是什么?

CompletableFuture 可以简单理解为 Future 的升级版,可以非常方便的进行任务的编排。

以往任务编排,需要借助 CountDownLatch、Semaphore、CyclicBarrier 等来实现,处理逻辑较为复杂。

本文源码层面分析 allOfanyOf 这两个方法。抽丝剥茧,深入剖析 CompletableFuture 的原理。

二、代码示例

1. allOf(CompletableFuture<?>... cfs)

allOf : 可以探测,所有的异步任务,是否全部执行完了。
举个例子,集齐七颗龙珠可以召唤神龙。
就可以分七个支线,每条支线找一颗龙珠。

// 不用CompletableFuture版本 

    public static void main(String[] args) throws InterruptedException {
        ExecutorService pool = Executors.newCachedThreadPool();
        long start = System.currentTimeMillis();
        int count = 7;
        CountDownLatch latch = new CountDownLatch(count);
        for (int i = 0; i < count; i++) {
            int num = i + 1;
            pool.submit(() -> {
                new Dragon(num).findPreciousness();
                latch.countDown();
            });
        }
        String mainThread = Thread.currentThread().getName();
        System.out.println(mainThread + "收集龙珠,等待中……");
        latch.await();
        long end = System.currentTimeMillis() - start;
        System.out.println(mainThread + "线程历经" + end + "毫秒,终于集齐七颗龙珠,开始召唤神龙!!");
        pool.shutdown();
    }

    static class Dragon {

        private int num;

        Dragon(int num) {
            this.num = num;
        }

        private void findPreciousness() {
            int cost = RandomUtil.randomInt(1, num + 1) * 1000;
            try {
                Thread.sleep(cost);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "线程历经 " + cost + " 毫秒找到第 " + num + " 颗龙珠");
        }
    }

这个异步版本,用 CountDownLatch 来保证,所有异步任务都执行完。

如果不知道它怎么用,可以看我之前的文章《CountDownLatch源码分析》

集齐七颗龙珠的任务,交给线程池异步处理,

当子任务全部完成时,主线程继续执行,否则阻塞。

再看下,用 CompletableFutureallOf 方法实现的版本

    public static void main(String[] args) throws InterruptedException {
        ExecutorService pool = Executors.newCachedThreadPool();
        long start = System.currentTimeMillis();
        int count = 7;
        CompletableFuture[] temp = new CompletableFuture[count];
        for (int i = 0; i < count; i++) {
            int num = i + 1;
            temp[num - 1] = CompletableFuture.runAsync(() -> new Dragon(num).findPreciousness(), pool);
        }
        String mainThread = Thread.currentThread().getName();
        System.out.println(mainThread + "收集龙珠,等待中……");
        CompletableFuture.allOf(temp).join();
        long end = System.currentTimeMillis() - start;
        System.out.println(mainThread + "线程历经" + end + "毫秒,终于集齐七颗龙珠,开始召唤神龙!!");
        pool.shutdown();
    }

示例中 allOf 方法,实现了 CountDownLatch 的功能。

当然,如果你对 stream 写法很熟悉,那代码可以很简单。

    public static void main(String[] args) throws InterruptedException {
        ExecutorService pool = Executors.newCachedThreadPool();
        CompletableFuture[] futures = IntStream.rangeClosed(1, 7)
                .mapToObj(num -> new Dragon(num))
                .map(dragon -> CompletableFuture.runAsync(dragon::findPreciousness, pool))
                .toArray(size -> new CompletableFuture[size]);
        CompletableFuture.allOf(futures).join();
        pool.shutdown();
    }

2. anyOf(CompletableFuture<?>... cfs)

anyOf : 所有的异步任务中,任意一个完成了,就可以被探测到。
举个例子,集齐七颗龙珠可以召唤神龙,这个任务比较难。
找到任意一颗,就摆两桌庆祝下,不管是哪个支线找到的。

    public static void main(String[] args) throws InterruptedException {
        ExecutorService pool = Executors.newCachedThreadPool();
        CompletableFuture[] futures = IntStream.rangeClosed(1, 7)
                .mapToObj(num -> new Dragon(num))
                .map(dragon -> CompletableFuture.runAsync(dragon::findPreciousness, pool))
                .toArray(size -> new CompletableFuture[size]);
        CompletableFuture.anyOf(futures).join();
        pool.shutdown();

只需要修改一行代码,就可以了。把 allOf 改为 anyOf 即可。

同样用 CountDownLatch 也可以实现,也是改一行

CountDownLatch latch = new CountDownLatch(1);

CountDownLatch 是怎么实现的,不是本文的重点,不展开说了。

有兴趣的话,可以看我之前的文章《CountDownLatch源码分析》。

三、allOf 原理分析

1. 整体流程解释

在这里插入图片描述
截图中的1,指主线程提交任务到线程池。

截图中的2,指主线程调用 allOf 方法,生成 一个 CompletableFuture 对象 allOfFuture

线程池的工作线程,会执行异步任务,所有异步任务执行完,会给刚刚那个 allOfFuture 对象打个标识。

截图中的3,是主线程会判断那个标识,标识存在,说明所有异步任务已执行完。

若标识不存在,主线程阻塞,等待所有异步任务执行结束时,主线程会被唤醒。

以上是基本的原理,很重要。网上的文章很少会介绍这个。

我是前前后后,看了好多遍才想明白的,我也想找现成的文章看,可是压根就没有。

先有个印象,接着一条一条详细说。

2. runAsync 任务提交


  public static CompletableFuture<Void> runAsync(Runnable runnable,
                                                 Executor executor) {
      return asyncRunStage(screenExecutor(executor), runnable);
  }
  
  static CompletableFuture<Void> asyncRunStage(Executor e, Runnable f) {
      if (f == null) throw new NullPointerException();
      CompletableFuture<Void> d = new CompletableFuture<Void>();
      e.execute(new AsyncRun(d, f));
      return d;
  }
  
  static final class AsyncRun extends ForkJoinTask<Void>
          implements Runnable, AsynchronousCompletionTask {
      CompletableFuture<Void> dep; Runnable fn;
      AsyncRun(CompletableFuture<Void> dep, Runnable fn) {
          this.dep = dep; this.fn = fn;
      }
   }

相关代码,简洁明了,生成异步任务,提交到线程池,最终返回的是一个CompletableFuture对象

在这里插入图片描述
简单画了张图,runAsync 返回的对象是截图中黄色的那个,给它的编号是 11

提交到线程池的对象,是截图中红色那个,其中 dep 属性指向了 11 的对象。

线程池的工作原理 ,本文不讲。任务执行时,会调用任务对象的 run() 方法。

如果对线程池的工作原理不清楚,可以看这篇《线程池工作原理》

顺便说一句,CompletableFuture 两个属性需要关注,

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

result 是任务执行的结果, stack 等会再说,先记住它是链式结构

在七龙珠的任务中,会产生7个任务,同时也会返回7个 CompletableFuture 对象。

3. allOf 对象编排

  public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) {
      return andTree(cfs, 0, cfs.length - 1);
  }
  static CompletableFuture<Void> andTree(CompletableFuture<?>[] cfs,
                                         int lo, int hi) {
      CompletableFuture<Void> d = new CompletableFuture<Void>();
      if (lo > hi) // empty
          d.result = NIL;
      else {
          CompletableFuture<?> a, b;
          int mid = (lo + hi) >>> 1;
          if ((a = (lo == mid ? cfs[lo] :
                    andTree(cfs, lo, mid))) == null ||
              (b = (lo == hi ? a : (hi == mid+1) ? cfs[hi] :
                    andTree(cfs, mid+1, hi)))  == null)
              throw new NullPointerException();
          if (!d.biRelay(a, b)) {
              BiRelay<?,?> c = new BiRelay<>(d, a, b);
              a.bipush(b, c);
              c.tryFire(SYNC);
          }
      }
      return d;
  }

allOf 方法,是递归方法,有点不好理解,以七龙珠为例,画图来讲解。

首次调用 andTree(cfs, 0, 6) 时,a 就是 andTree(cfs, 0, 3) ,b 就是 andTree(cfs, 4, 6) 。
第二次调用 andTree(cfs, 0, 3) 时,a 就是 andTree(cfs, 0, 1) ,b 就是 andTree(cfs, 2, 3) ……
在这里插入图片描述
画了个大概的图,异步任务1的dep指向 cfs[0],,先记住这个,下文会用。

详细说下 andTree(cfs, 0, 1) 这个方法的调用 ,代码可简化为


    CompletableFuture<Void> d = new CompletableFuture<Void>();
    CompletableFuture<?> a = cfs[0];
    CompletableFuture<?> b = cfs[1];
    if (!d.biRelay(a, b)) {
        CompletableFuture.BiRelay<?,?> c = new CompletableFuture.BiRelay<>(d, a, b);
        a.bipush(b, c);
        c.tryFire(SYNC);
    }
    return d;

3.1 d.biRelay(a, b),判断任务是否执行

首先看 d.biRelay(a, b) 这个方法。假设 任务1,任务2都还没执行,

CompletableFuture 的 result 字段,是volatile 修饰的,可保证线程间可见性。

可简单理解为:result 是 null 任务没执行,不是 null 任务已执行。


  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;
      if (result == null) { // 若进入这个分支,一定会将 result 赋值
          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;
  }

假设任务1 和任务2都没有执行 ,if (!d.biRelay(a, b)) {} 条件成立,进入这个分支。

3.2 a.bipush(b, c),对象编排


     BiRelay<?,?> c = new BiRelay<>(d, a, b);
     a.bipush(b, c);

前面说过 CompletableFuture 还有一个属性 stack,类型是 Completion
这里的 new BiRelay<>(d, a, b) ,其中 BiRelay 就是 Completion 的一个子类。

 @SuppressWarnings("serial")
 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);
     }
 }

BiRelay 对象中有 3 个属性,dep, src, snd,画张图,并标明指向关系
在这里插入图片描述
BiRelay<?,?> c = new BiRelay<>(d, a, b); 这句代码中:
c 指图中A对象,a 指 11 对象,b 指 12 对象, d 指图中 21 对象


  final void bipush(CompletableFuture<?> b, BiCompletion<?,?,?> c) {
      if (c != null) {
          Object r;
          while ((r = result) == null && !tryPushStack(c))
              lazySetNext(c, null); // clear on failure
          if (b != null && b != this && b.result == null) {
              Completion q = (r != null) ? c : new CoCompletion(c);
              while (b.result == null && !b.tryPushStack(q))
                  lazySetNext(q, null); // clear on failure
          }
      }
  }

这代码看着就头大,主要是对象太多,容易迷,我画了图,多看几遍就好。
上面是 a.bipush(b, c) 的代码,还是假设 任务 1,任务2 没有执行,a、b 各自的 result 就是 null

tryPushStack( c ); 意思就是 把 a 的 stack 设置为 c,

b.tryPushStack(q); 意思就是把 b 的 stack 设置为 q,

q 是 new CoCompletion©,即 q 的 base 设置为 c;

对象比较多,看图吧

在这里插入图片描述

3.3 c.tryFire(SYNC); 若任务执行完,设置 result


   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))
           return null;
       src = null; snd = null; dep = null;
       return d.postFire(a, b, mode);
   }

这个方法,下文还会讲到,这里 !d.biRelay(a = src, b = snd) 会返回 true, tryFire 返回 null.

至此 andTree(cfs, 0, 1) 结束,结合上图说,该方法执行时,会创建两个对象 A 和 B

11 对象的 stack 指向A 。 12 对象 的 stack 指向 B

把这个过程理顺了,咱就想想 andTree(cfs, 0, 6) ,这个是递归,多想几遍就清楚了

如果画成图,大概是下面这个鸟样!!!!

在这里插入图片描述
最终这个 andTree(cfs, 0, 6) ,返回值就是 41 这个对象。

异步线程在执行完所有任务时,会给 41对象 result 赋值

主线程通过调用 join() 方法,判断 result 是否有值,来决定是否阻塞

4. join() 方法的阻塞与唤醒。

这里先假设,调用 join() 方法时,异步任务还没有执行完


  public T join() {
      Object r;
      return reportJoin((r = result) == null ? waitingGet(false) : r);
  }

reportJoin 这个方法可忽略,简单理解,把入参原样返回。(任务执行异常先不说)

result 有值,就直接返回,result 无值,调用 waitingGet(false)

4.1 waitingGet(false),阻塞或结束

    private Object waitingGet(boolean interruptible) {
        Signaller q = null;
        boolean queued = false;
        int spins = -1;
        Object r;
        while ((r = result) == null) {
            if (spins < 0)
                spins = SPINS;
            else if (spins > 0) {
                if (ThreadLocalRandom.nextSecondarySeed() >= 0)
                    --spins;
            }
            else if (q == null)
                q = new Signaller(interruptible, 0L, 0L);
            else if (!queued)
                queued = tryPushStack(q);
            else if (interruptible && q.interruptControl < 0) {
                q.thread = null;
                cleanStack();
                return null;
            }
            else if (q.thread != null && result == null) {
                try {
                    ForkJoinPool.managedBlock(q);
                } catch (InterruptedException ie) {
                    q.interruptControl = -1;
                }
            }
        }
        if (q != null) {
            q.thread = null;
            if (q.interruptControl < 0) {
                if (interruptible)
                    r = null; // report interruption
                else
                    Thread.currentThread().interrupt();
            }
        }
        postComplete();
        return r;
    }

while ((r = result) == null) {} 这句,跳出循环的条件就是 result 有值。

spins 这个就是自旋一会儿,可忽略,把 while 里的代码精简如下


	while ((r = result) == null) {
		// 自旋数次之后,执行下面的代码
       Signaller q = new Signaller(interruptible, 0L, 0L);	
       tryPushStack(q) // 把 41 stack 设置为 q
       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) {
           WorkQueue w = wt.workQueue;
           while (!blocker.isReleasable()) {
               if (p.tryCompensate(w)) {
                   try {
                       do {} while (!blocker.isReleasable() &&
                                    !blocker.block());
                   } finally {
                       U.getAndAddLong(p, CTL, AC_UNIT);
                   }
                   break;
               }
           }
       }
       else {
           do {} while (!blocker.isReleasable() &&
                        !blocker.block());
       }
   }

managedBlock 这个方法看起来,也很复杂,本文中是主线程调用的 join。
也就是普通线程,所有直接进 else 分支。


   do {} while (!blocker.isReleasable() &&
                !blocker.block());

   public boolean isReleasable() {
       if (thread == null)
           return true;
       if (Thread.interrupted()) {
           int i = interruptControl;
           interruptControl = -1;
           if (i > 0)
               return true;
       }
       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();
   }

isReleasable 这个方法中,三个 if 条件都不满足,返回 false;
block() 方法中,会进入第二个分支,会调用 park 方法,那进入阻塞,等待唤醒。

park 与 unpark 是怎么回事,可以看我之前的文章 《 park 和 unpark

至此,分析的 join() 方法的阻塞过程。唤醒过程等会儿再说。

4.2 tryFire 唤醒主线程


  static final class Signaller extends Completion
      implements ForkJoinPool.ManagedBlocker {

      final CompletableFuture<?> tryFire(int ignore) {
          Thread w; // no need to atomically claim
          if ((w = thread) != null) {
              thread = null;
              LockSupport.unpark(w);
          }
          return null;
      }
  }

Signaller 类中 tryFire 方法会唤醒主线程,哪里调用的这个方法,等会儿再讲。

四、AsyncRun 原理分析

前面讲将任务提交到线程池时,e.execute(new AsyncRun(d, f));

AsyncRun 重写了 run 方法,线程池的工作线程会调用 run 方法。


 static final class AsyncRun extends ForkJoinTask<Void>
         implements Runnable, AsynchronousCompletionTask {
         
     CompletableFuture<Void> dep; Runnable fn;
     
     AsyncRun(CompletableFuture<Void> dep, Runnable fn) {
         this.dep = dep; this.fn = fn;
     }
     
     public void run() {
         CompletableFuture<Void> d; Runnable f;
         if ((d = dep) != null && (f = fn) != null) {
             dep = null; fn = null;
             if (d.result == null) {
                 try {
                     f.run();
                     d.completeNull();
                 } catch (Throwable ex) {
                     d.completeThrowable(ex);
                 }
             }
             d.postComplete();
         }
     }
 }

1. d.completeNull() :result 赋值

前面的那张图,我重新贴出来
在这里插入图片描述
龙珠1 这个任务,dep 会指向 对象 11。 run 方法中 d.completeNull(); 就是设置 result


	d.completeNull();
	
	final boolean completeNull() {
	    return UNSAFE.compareAndSwapObject(this, RESULT, null,NIL);
	}
	

2. d.postComplete() :链式处理


 final void postComplete() {
     CompletableFuture<?> f = this; Completion h;
     while ((h = f.stack) != null ||
            (f != this && (h = (f = this).stack) != null)) {
         CompletableFuture<?> d; Completion t;
         if (f.casStack(h, t = h.next)) {
             if (t != null) {
                 if (f != this) {
                     pushStack(h);
                     continue;
                 }
                 h.next = null;    // detach
             }
             f = (d = h.tryFire(NESTED)) == null ? this : d;
         }
     }
 }

这是核心方法,咱们慢慢说。下图做了简化,重点看与对象11相关的。
在这里插入图片描述
对象11的 postComplete 方法,f 就是对象11, h 就是对象A,第一次进入 while 循环。

if (f.casStack(h, t = h.next)) 这句执行结果,就是 对象 11 stack 赋值为 null.
其中 t = h.next ,那么 t 就是 null.


  if (f.casStack(h, t = h.next)) {
      f = (d = h.tryFire(NESTED)) == null ? this : d;
  }

 final boolean casStack(Completion cmp, Completion val) {
     return UNSAFE.compareAndSwapObject(this, STACK, cmp, val);
 }

h.tryFire(NESTED) ,即 A对象的 tryFire(-1) ,先说结果:
如果 对象 11 和 对象12,result 都有值,返回对象21,否则返回 null。
如果 对象 11 和 对象12,result 都有值,返回对象21,否则返回 null。
如果 对象 11 和 对象12,result 都有值,返回对象21,否则返回 null。

  1. h.tryFire(NESTED) 返回 null

如果 h.tryFire(NESTED) 返回null, 会造成 postComplete 方法结束。

因为 f = (d = h.tryFire(NESTED)) == null ? this : d; f 赋值 this,即 f 还是对象 11

此时 (h = f.stack) != null 不成立,因为对象 11 stack 已经被赋值 null
f != this && (h = (f = this).stack) != null) 也不成立,因为 f 就是 this。

退出 while 循环,d.postComplete() 方法结束。

总结下,龙珠1 这个任务执行后,对象 11 的 result 被赋值 , stack 被清空。
此时 若龙珠 2 这个任务没完成,postComplete 方法结束。

  1. h.tryFire(NESTED) 返回 对象 21

那就继续 while 循环, 对象 21 stack 赋值为 null.
h.tryFire(NESTED) ,即 G 对象的 tryFire(-1) ,如此循环下去。

看上面那张图,咱们是由 对象11 来触发的,
如果 12 没完成就结束,否则启动下一轮,
看 对象 21 22 都完成没,没完成结束,完成启动下一轮,看31和32。

postComplete() 方法,确实不好懂,结合图,多看几遍。

3. h.tryFire(-1) :关联判断

前面说了 如果 对象 11 和 对象12,result 都有值,返回对象21,否则返回 null。
是为了 把 while 循环,退出和继续的条件讲清楚,现在分析**对象A **的 tryFire(-1)

3.1 h.tryFire(-1) 什么情况下返回 null


 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))
         return null;
     src = null; snd = null; dep = null;
     return d.postFire(a, b, mode);
 }
 
 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;
     if (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;
 }

在这里插入图片描述
现在对象11的result是有值的,如果对象12的result无值,
biRelay 方法中,第1个 if 判断中 (s = b.result) == null)true,
也就是 tryFire 方法中 if ((d = dep) == null || !d.biRelay(a = src, b = snd))true

至此可说明,对象12的 result 无值时,h.tryFire(-1) 返回 null.

3.2 h.tryFire(-1) 什么情况下返回 返回对象 21

对象12的result有值时,d.biRelay 这个方法,进入第二个 if 分支。

此分支里有3种情况,自己看吧,但不论哪种情况,对象 21 的 result 都会被赋值。

tryFire 方法执行 src = null; snd = null; dep = null;,即 A对象三个属性被清空。
在这里插入图片描述

3.3 d.postFire(a, b, -1); d:对象21 , a:对象11 , b:对象12


    final CompletableFuture<T> postFire(CompletableFuture<?> a,
                                        CompletableFuture<?> b, int mode) {
        if (b != null && b.stack != null) { // clean second source
            if (mode < 0 || b.result == null)
                b.cleanStack();
            else
                b.postComplete();
        }
        return postFire(a, mode);
    }

    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;
    }

其中 b.cleanStack(); 是 把对象12 stack 设置为 null,细节自己看源码吧
postFire(a, mode),第 1 个 if 分支不会进,因为 a.stack 是 null ,
会进第二个 if 分支 返回 this,即返回对象 21。

五、 主线程的唤醒。

1. 总体流程小结

AsyncRun 原理分析结束,结合龙珠任务总结如下。
在这里插入图片描述

  1. 七龙珠任务,被 allOf 两两结合,层层缩减为对象 41
  2. 线程池执行 龙珠1任务时,检测龙珠2任务,若其没完成,结束。
  3. 若龙珠2 完成了,会循环检测 21、22,以此类推。
  4. 结束最晚的一个任务,会层层判断,给对象 41的result 赋值
  5. 主线程调用对象41的 join方法,判断 result 是否有值,来决定是否阻塞。

2. 主线程何时被唤醒

之前讲waitingGet(false) 方法时,对象 41的 stack 会被赋值。

	while ((r = result) == null) {
		// 自旋数次之后,执行下面的代码
       Signaller q = new Signaller(interruptible, 0L, 0L);	
       tryPushStack(q) // 把 41 stack 设置为 q
       ForkJoinPool.managedBlock(q); // 阻塞
	}

那对象 41的stack 是 SignallerpostComplete 方法最后会调用 其 tryFire

这里有 LockSupport.unpark(w); ,即是唤醒主线程。


    Signaller(boolean interruptible, long nanos, long deadline) {
        this.thread = Thread.currentThread();
        this.interruptControl = interruptible ? 1 : 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);
        }
        return null;
    }

当然,如果主线程调用 join 方法时,其 result 已经有值,那就不会阻塞了。

六、anyOf 原理分析

它与 allOf 大同小异,简单说下


    public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) {
        return orTree(cfs, 0, cfs.length - 1);
    }

    static CompletableFuture<Object> orTree(CompletableFuture<?>[] cfs,
                                            int lo, int hi) {
        CompletableFuture<Object> d = new CompletableFuture<Object>();
        if (lo <= hi) {
            CompletableFuture<?> a, b;
            int mid = (lo + hi) >>> 1;
            if ((a = (lo == mid ? cfs[lo] :
                      orTree(cfs, lo, mid))) == null ||
                (b = (lo == hi ? a : (hi == mid+1) ? cfs[hi] :
                      orTree(cfs, mid+1, hi)))  == null)
                throw new NullPointerException();
            if (!d.orRelay(a, b)) {
                OrRelay<?,?> c = new OrRelay<>(d, a, b);
                a.orpush(b, c);
                c.tryFire(SYNC);
            }
        }
        return d;
    }

区别很小 OrRelay<?,?> c = new OrRelay<>(d, a, b); 这行不一样


    static final class OrRelay<T,U> extends BiCompletion<T,U,Object> { // for Or
        OrRelay(CompletableFuture<Object> dep, CompletableFuture<T> src,
                CompletableFuture<U> snd) {
            super(null, dep, src, snd);
        }
        final CompletableFuture<Object> tryFire(int mode) {
            CompletableFuture<Object> d;
            CompletableFuture<T> a;
            CompletableFuture<U> b;
            if ((d = dep) == null || !d.orRelay(a = src, b = snd))
                return null;
            src = null; snd = null; dep = null;
            return d.postFire(a, b, mode);
        }
    }

    final boolean orRelay(CompletableFuture<?> a, CompletableFuture<?> b) {
        Object r;
        if (a == null || b == null ||
            ((r = a.result) == null && (r = b.result) == null))
            return false;
        if (result == null)
            completeRelay(r);
        return true;
    }

这个和 allOf 差别也很小,重点看这一个判断。

// allOf 里的版本
    if (a == null || (r = a.result) == null ||
        b == null || (s = b.result) == null)
        return false;
        
// anyOf 里的版本
    if (a == null || b == null ||
        ((r = a.result) == null && (r = b.result) == null))
        return false;

allOf 里是集齐七龙珠,当龙珠1找到时,
若龙珠2没找到时,s = b.result) == null 是 true,从而 tryFire 返回 null,终止。

anyOf 里找到任意一颗,当龙珠1找到时,(r = a.result) == null 这个条件是 flase,
(r = b.result) == null) 就不会判断,也就是龙珠2,找没找到可以忽略,tryFire 会继续。
进而一直 while 循环,直到 对象 41 的 result 赋值后才会结束。

大概就是这个意思,这个就是 anyOf 的原理,多看几遍就理解了。


总结

本文从源码层面,分析了 CompletableFuture 中 allOf / anyOf 两个方法

Doug Lea 真的是 YYDS,代码出了名的难懂。

但仔细理解,大神的代码,真的是精妙,尤其是多态运用,

让不同的行为,共用一套框架。

之所以我觉得难,不是代码的问题,是我功力不够。

再次膜拜中

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值