文章目录

  • 1. 关键组件
  • 1.1 操作的分类
  • 1.2 操作链的形成
  • 1.2.1 CompletableFuture 的结构
  • 1.2.2 Completion 的结构
  • 2. 实现原理
  • 2.1 异步任务的执行流程
  • 2.2 异步任务执行的源码分析
  • 2.2.1 主线程的执行流程
  • 2.2.2 任务在线程池中的执行流程


1. 关键组件

1.1 操作的分类

在 Java CompletableFuture(1)-使用详解 中提到过,CompletableFuture 可以对执行链上的各个操作进行组合编排,其实在 CompletableFuture 内部实现中所有操作都只分为了以下两大类

  • 单依赖操作:
    单依赖操作只依赖一个前置操作,只要前置操作完成了就可以执行
  • 双依赖操作
    与单依赖操作的定义类似,双依赖操作依赖的前置操作为两个,双依赖操作的执行需要根据其两个前置操作的执行情况来确定

除了以上条件,根据当前操作是否需要依赖前置操作的执行结果也可以将 CompletableFuture 的操作进行划分,具体如下图所示

java decom java的completefuture二次封装_java decom

java decom java的completefuture二次封装_异步_02

1.2 操作链的形成

CompletableFuture
        .supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "supply");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return Integer.MAX_VALUE;
        })
        .thenAcceptAsync(i -> {
            System.out.println("accept:" + System.currentTimeMillis());
        })
        .thenRunAsync(() -> {
            System.out.println("apply:" + System.currentTimeMillis());
        });

在 Java 8 Stream(2)-原理解析 一文中,笔者分析过 Stream 的实现是把一个操作称为一个 stage,调用方法则构造对应的对象来记录操作行为,这一点在 CompletableFuture 中也是一样的。CompletableFuture 的每一个异步操作方法调用都会生成一个 Completion 对象,以上代码对应的操作执行流程如下:

  1. CompletableFuture 的相关方法被调用时,返回的还是一个 CompletableFuture 对象,但是其内部会生成对应的操作对象。以 CompletableFuture#supplyAsync() 为例,内部生成的操作对象为 AsyncSupply,这个AsyncSupply对象的 dep 属性会指向新生成的 CompletableFuture 对象
  2. 当下一个操作方法被调用时,又会生成新的CompletableFuture 对象及其操作对象。当前 CompletableFuture 对象会根据自身对应的操作是否执行完成来决定要不要把新生成的操作对象添加到内部维护的 stack 无锁并发栈,如果新生成的操作对象入栈,则当前操作执行完成后会回调栈顶的操作的执行方法,从而形成了一条执行链路。另外新生成的操作对象会把上一个 CompletableFuture 对象赋值给自己的 src 属性,这样就可以通过检查上一个CompletableFuture 对象的 result 属性来判断自身依赖的前置操作是否完成,从而决定自身的执行时机

java decom java的completefuture二次封装_异步_03

如果依赖操作比较多且其互相之间关系比较复杂,则最终会形成如下图所示的依赖拓扑

java decom java的completefuture二次封装_多线程_04

1.2.1 CompletableFuture 的结构

CompletableFuture 内部属性不多,最核心的是如下两个:

  1. result:代表当前任务的执行结果,如果任务没有执行完毕则为 NULL。需注意其使用了 volatile 修饰,可以保证修改对其他线程的可见性
  2. stack:用于保存依赖当前操作的其他操作对象,其实是单向链表结构,元素都从链表头部插入。当前操作执行完毕时会从链表头部遍历这个 stack,将其中保存的其他操作取出来尝试执行
public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
    volatile Object result;       // Either the result or boxed AltResult
    volatile Completion stack;    // Top of Treiber stack of dependent actions
    ......
}
1.2.2 Completion 的结构

Completion 是操作对象最顶层的抽象结构,可以看到其内部其实只有一个 next 属性用于保存下一个 Completion 对象

abstract static class Completion extends ForkJoinTask<Void>
        implements Runnable, AsynchronousCompletionTask {
        volatile Completion next;      // Treiber stack link

        /**
         * Performs completion action if triggered, returning a
         * dependent that may need propagation, if one exists.
         *
         * @param mode SYNC, ASYNC, or NESTED
         */
        abstract CompletableFuture<?> tryFire(int mode);

        /** Returns true if possibly still triggerable. Used by cleanStack. */
        abstract boolean isLive();

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

UniCompletion 是单依赖操作的抽象类,其比较关键的属性如下:

  1. executor:执行器对象,其实也就是异步任务的执行线程池
  2. dep:该操作对象自身对应的 CompletableFuture 对象
  3. src:该操作对象的前置操作对应的 CompletableFuture 对象
abstract static class UniCompletion<T,V> extends Completion {
        Executor executor;                 // executor to use (null if none)
        CompletableFuture<V> dep;          // the dependent to complete
        CompletableFuture<T> src;          // source for action

        UniCompletion(Executor executor, CompletableFuture<V> dep,
                      CompletableFuture<T> src) {
            this.executor = executor; this.dep = dep; this.src = src;
        }

BiCompletion 是双依赖操作的抽象类,其继承于 UniCompletion,可以看到比较关键的是多出了如下属性:

snd:该操作对象的第二个前置操作对应的 CompletableFuture 对象

abstract static class BiCompletion<T,U,V> extends UniCompletion<T,V> {
        CompletableFuture<U> snd; // second source for action
        BiCompletion(Executor executor, CompletableFuture<V> dep,
                     CompletableFuture<T> src, CompletableFuture<U> snd) {
            super(executor, dep, src); this.snd = snd;
        }
    }

2. 实现原理

2.1 异步任务的执行流程

java decom java的completefuture二次封装_java decom_05

以上一节代码为例,每一个异步方法调用生成的操作对象都是一个 ForkJoinTask,其最终都会被提交到 ForkJoinPool 执行。以上为 CompletableFuture 异步任务的执行示意图,读者可以结合以下对各个任务执行的源码解析理解其执行原理

2.2 异步任务执行的源码分析

java decom java的completefuture二次封装_多线程_06

2.2.1 主线程的执行流程
  1. CompletableFuture#supplyAsync() 是异步任务创建的入口,该方法的逻辑比较简单,可以看到只是将外部传入的 lambda 表达式 supplier内部的默认执行器 asyncPool 入参调用了 CompletableFuture#asyncSupplyStage() 方法
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
    return asyncSupplyStage(asyncPool, supplier);
}
  1. CompletableFuture#asyncSupplyStage() 方法的逻辑比较清晰,同时也比较关键,可分为以下几个步骤:
  1. 首先检查外部传入的 lambda 表达式是否为 NULL,如是则说明使用方根本没有指定异步任务的执行逻辑,抛异常
  2. 调用 CompletableFuture 构造方法新建一个 CompletableFuture 对象,该对象将用于方法返回,供外部持有引用
  3. 使用新建的 CompletableFuture 对象和外部传入的 lambda 表达式构建 AsyncSupply 异步任务对象,并将其提交到线程池执行
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;
}
  1. AsyncSupply 的结构如下所示,可以看到其继承于 ForkJoinTask并且实现了 Runnable 接口,那么当其在 ForkJoinPool 线程池执行时会触发AsyncSupply#exec() 方法,在其他线程池执行时会触发 AsyncSupply#run() 方法。 另外其内部比较关键的属性如下:
  1. dep:AsyncSupply 操作对象自身对应的 CompletableFuture 对象
  2. fn:异步任务需要执行的业务逻辑

AsyncSupply 异步任务提交到线程池中通常很快就会执行,本文示例中为了更好地解析流程使用了线程休眠模拟耗时操作,这样就可以假设 AsyncSupply 异步任务在较长时间内都没有执行完成,也就是没有调用d.completeValue()通过 CAS 设置执行结果

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;
             if (d.result == null) {
                 try {
                     d.completeValue(f.get());
                 } catch (Throwable ex) {
                     d.completeThrowable(ex);
                 }
             }
             d.postComplete();
         }
     }
 }
  1. 主线程将异步任务提交到线程池后继续执行CompletableFuture#thenAcceptAsync() 方法,可以看到此处操作与步骤1类似,都是调用对应的 stage 后缀方法记录操作
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action) {
     return uniAcceptStage(asyncPool, action);
 }
  1. CompletableFuture#uniAcceptStage() 方法的实现很清晰,关键点如下:
  1. 首先依然是检查代表异步任务逻辑的 lambda 表达式,如果为 NULL 说明没有需要处理的任务逻辑,抛异常
  2. 新生成本次操作的 CompletableFuture 对象
  3. 检查传入的执行器对象是否为 NULL,此处显然不为 NULL,则进入 if 代码块执行。可以看到这部分代码逻辑也比较清晰,首先是将上一个 CompletableFuture 对象入参生成 UniAccept 操作对象,其次是调用 CompletableFuture#push() 方法尝试将新生成的操作对象添加到上一个 CompletableFuture 对象的栈中,最后调用 UniAccept#tryFire() 方法尝试执行本次操作的任务逻辑
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;
 }
  1. UniAccept 继承于 UniCompletion,可以看到相较于 UniCompletion其内部新增了一个 fn 属性用于保存任务逻辑,其余参数与父类完全相同
static final class UniAccept<T,V> extends UniCompletion<T,V> {
     Function<? super T,? extends V> fn;
     UniAccept(Executor executor, CompletableFuture<V> dep,
              CompletableFuture<T> src,
              Function<? super T,? extends V> fn) {
         super(executor, dep, src); this.fn = fn;
     }
     ......
 }
  1. CompletableFuture#push() 方法为本次操作的上一个 CompletableFuture 对象调用,也就是说当前操作所依赖的前置操作的CompletableFuture 对象会尝试将当前操作压入栈中,具体做法如下:
  1. 检查上一个 CompletableFuture 对象的 result 属性,如果为 NULL 说明上一个操作尚未完成,本次操作自然也无法执行,只能尝试将本次操作暂存到上一个操作对应的 CompletableFuture 对象的无锁并发栈中。while 循环保证 CAS 插入失败可以不断检查上一个操作是否完成,从而决定是否重试插入
  2. tryPushStack()方法逻辑比较简单,就是通过 CAS 把当前操作对象插入到 stack 链表头部
final void push(UniCompletion<?,?> c) {
     if (c != null) {
         while (result == null && !tryPushStack(c))
             lazySetNext(c, null); // clear on failure
     }
 }
final boolean tryPushStack(Completion c) {
     Completion h = stack;
     lazySetNext(c, h);
     return UNSAFE.compareAndSwapObject(this, STACK, h, c);
 }
  1. UniAccept#tryFire() 方法的实现如下,可以看到在 if 判断中第一个条件在本次调用必然不会成立,则将执行 CompletableFuture#uniAccept() 方法

需注意此处传人的 mode 为 SYNC,则 CompletableFuture#uniAccept() 方法的第三个参数为 UniApply 对象

// Modes for Completion.tryFire. Signedness matters.
 static final int SYNC   =  0;
 static final int ASYNC  =  1;
 static final int NESTED = -1;
final CompletableFuture<Void> tryFire(int mode) {
         CompletableFuture<Void> d; CompletableFuture<T> a;
         if ((d = dep) == null ||
             !d.uniAccept(a = src, fn, mode > 0 ? null : this))
             return null;
         dep = null; src = null; fn = null;
         return d.postFire(a, mode);
     }
  1. CompletableFuture#uniAccept() 方法的逻辑有点绕,比较关键的点如下:
  1. 首先根据当前操作对象保存的上一个 CompletableFuture 对象的 result 字段判断上一个操作是否完成,未完成直接返回 false 结束
  2. 如果上一个操作已经完成了,则检查当前操作对应的 CompletableFuture 对象的 result 字段,判断自身是否已经执行完成
  3. 自身未执行完成的话首先检查上一个操作是否是正常完成(任务异常结束或者返回空结果时,CompletableFuture 对象的 result 为 AltResult 对象
  4. 接下来检查当前操作是否可以执行,此处主要根据传入的参数 c 来进行判断。如果c 不为 NULL ,则继续调用 UniAccept#claim() 方法来进行判断。如果方法返回 false,则不可以执行任务逻辑;其他情况可以往下执行任务逻辑,并调用 CompletableFuture#completeNull() 方法来设置结果,完成任务处理
final <S> boolean uniAccept(CompletableFuture<S> a,
                             Consumer<? super S> f, UniAccept<S> 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;
             f.accept(s);
             completeNull();
         } catch (Throwable ex) {
             completeThrowable(ex);
         }
     }
     return true;
 }
  1. UniAccept#claim() 方法其实是父类实现的UniCompletion#claim(),这个方法的实现必须要说明

该方法会 CAS 检查异步任务的执行状态,如果未执行则将其提交给线程池执行,如果已经执行则返回。因为在多线程环境下每个操作其实都有两个执行的触发点,此方法的目的主要是为了保证每个操作只能被执行一次

  1. 假设此时本文示例的第一个异步任务已经执行完成,而又还没有触发其操作栈中的其他操作,则新的操作对象在主线程中生成时可能被提交到线程池执行,也就是当前分析的这个流程
  2. 在线程池环境中,当前置操作完成后触发操作栈中操作执行时,本次操作的执行依然会从 UniAccept#tryFire()方法开始,这样不同的线程执行到UniAccept#claim()方法时,其中一个就会被阻断,也就保证了任务逻辑只执行一次

为便于分析,本文假设在主线程中 thenAcceptAsync 操作生成的异步任务提交到了线程池,但是其执行的时序比由上一个操作触发的时序靠后。当然,实际上 thenAcceptAsync 操作生成的异步任务也有可能在上一个操作执行完成后提交到线程池,这样主线程的提交动作就会被阻断了

final boolean claim() {
        Executor e = executor;
        if (compareAndSetForkJoinTaskTag((short)0, (short)1)) {
            if (e == null)
                return true;
            executor = null; // disable
            e.execute(this);
        }
        return false;
    }
  1. 回到步骤5CompletableFuture#uniAcceptStage() 方法的执行到此结束,主线程中后续的CompletableFuture#thenRunAsync() 操作与 CompletableFuture#thenAcceptAsync()的处理流程大同小异,不再赘述
2.2.2 任务在线程池中的执行流程
  1. AsyncSupply 异步任务被提交到了 ForkJoinPool 线程池,则其执行时AsyncSupply#exec() 方法触发,可以看到 AsyncSupply#exec() 方法其实主要逻辑是调用 AsyncSupply#run() 方法。AsyncSupply#run() 方法的逻辑比较简单:
  1. 执行保存的 lambda 表达式,然后将执行结果通过 CAS 设置到其对应的 CompletableFuture 对象中
  2. 调用 CompletableFuture#postComplete() 方法触发 CompletableFuture 操作栈中保存的其他操作
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;
             if (d.result == null) {
                 try {
                     d.completeValue(f.get());
                 } catch (Throwable ex) {
                     d.completeThrowable(ex);
                 }
             }
             d.postComplete();
         }
     }
  1. CompletableFuture#postComplete() 方法检查是否有下一个操作需要执行,如果需要便会触发该任务的执行,其比较关键的步骤如下:
  1. 如果 CompletableFuture 的操作栈不为空,则 CAS 从操作栈的栈顶取出待执行的操作
  2. 执行代码 h.tryFire(NESTED) 触发下一个操作的处理方法,执行业务逻辑,并返回一个 CompletableFuture 对象
  3. while 循环,如果步骤2返回的 CompletableFuture 对象不为 NULL 并且操作栈不为空,则将其操作栈内的操作依次添加到当前 CompletableFuture 对象的操作栈中,再取当前栈顶操作执行,重复这个步骤。这个部分读者可结合本文1.2 节操作链的拓扑结构理解
final void postComplete() {
     /*
      * On each step, variable f holds current dependents to pop
      * and run.  It is extended along only one path at a time,
      * pushing others to avoid unbounded recursion.
      */
     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;
         }
     }
 }
  1. 示例代码中 h.tryFire(NESTED) 实际触发的是UniAccept#tryFire() 方法,但是需要注意此处入参为 NESTED,则其最终会在 2.2.1 节步骤10处阻断,并不会真正执行 thenAcceptAsync 操作的任务逻辑,此部分不再赘述
  2. 2.2.1 节主线程的执行流程中,我们假设 thenAcceptAsync 操作对应的异步任务 UniAccept 由主线程提交到了线程池,则可以知道当其执行时必然也以 UniAccept#exec() 方法作为入口,实际上该方法为父类的实现Completion#exec()
public final void run()                { tryFire(ASYNC); }
public final boolean exec()            { tryFire(ASYNC); return true; }
  1. UniAccept#tryFire() 方法此时的入参为 ASYNC,则调用 CompletableFuture#uniAccept() 方法的第三个参数为 NULL
final CompletableFuture<Void> tryFire(int mode) {
         CompletableFuture<Void> d; CompletableFuture<T> a;
         if ((d = dep) == null ||
             !d.uniAccept(a = src, fn, mode > 0 ? null : this))
             return null;
         dep = null; src = null; fn = null;
         return d.postFire(a, mode);
     }
  1. CompletableFuture#uniAccept() 方法的解析可参考2.2.1 节步骤9,此时可以看到参数 c 为 NULL,则任务逻辑顺利往下执行,任务执行完成
final <S> boolean uniAccept(CompletableFuture<S> a,
                             Consumer<? super S> f, UniAccept<S> 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;
             f.accept(s);
             completeNull();
         } catch (Throwable ex) {
             completeThrowable(ex);
         }
     }
     return true;
 }
  1. 回到步骤5CompletableFuture#uniAccept() 方法返回 true,则继续调用CompletableFuture#postFire() 方法做收尾工作。这个方法会检查源 CompletableFuture 对象 a当前 CompletableFuture 对象 d的栈是否有待执行任务,如果有分别调用 CompletableFuture#postComplete() 方法将其操作栈中的操作执行掉。不过在调用前还会检查 mode 的值,如果 mode 为 NESTED(-1),直接返回当前 CompletableFuture对象,该对象的栈任务由其他线程执行。至此,异步任务在线程池中的执行分析告一段落
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;
 }