文章目录
- 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
的操作进行划分,具体如下图所示
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
对象,以上代码对应的操作执行流程如下:
- 当
CompletableFuture
的相关方法被调用时,返回的还是一个CompletableFuture
对象,但是其内部会生成对应的操作对象。以CompletableFuture#supplyAsync()
为例,内部生成的操作对象为AsyncSupply
,这个AsyncSupply
对象的dep
属性会指向新生成的CompletableFuture
对象- 当下一个操作方法被调用时,又会生成新的
CompletableFuture
对象及其操作对象。当前CompletableFuture
对象会根据自身对应的操作是否执行完成来决定要不要把新生成的操作对象添加到内部维护的stack
无锁并发栈,如果新生成的操作对象入栈,则当前操作执行完成后会回调栈顶的操作的执行方法,从而形成了一条执行链路。另外新生成的操作对象会把上一个CompletableFuture
对象赋值给自己的src
属性,这样就可以通过检查上一个CompletableFuture
对象的result
属性来判断自身依赖的前置操作是否完成,从而决定自身的执行时机
如果依赖操作比较多且其互相之间关系比较复杂,则最终会形成如下图所示的依赖拓扑
1.2.1 CompletableFuture 的结构
CompletableFuture
内部属性不多,最核心的是如下两个:
result
:代表当前任务的执行结果,如果任务没有执行完毕则为NULL
。需注意其使用了volatile
修饰,可以保证修改对其他线程的可见性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
是单依赖操作的抽象类,其比较关键的属性如下:
executor
:执行器对象,其实也就是异步任务的执行线程池dep
:该操作对象自身对应的CompletableFuture
对象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 异步任务的执行流程
以上一节代码为例,每一个异步方法调用生成的操作对象都是一个
ForkJoinTask
,其最终都会被提交到ForkJoinPool
执行。以上为CompletableFuture
异步任务的执行示意图,读者可以结合以下对各个任务执行的源码解析理解其执行原理
2.2 异步任务执行的源码分析
2.2.1 主线程的执行流程
CompletableFuture#supplyAsync()
是异步任务创建的入口,该方法的逻辑比较简单,可以看到只是将外部传入的 lambda 表达式 supplier 和内部的默认执行器 asyncPool 入参调用了CompletableFuture#asyncSupplyStage()
方法
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return asyncSupplyStage(asyncPool, supplier);
}
CompletableFuture#asyncSupplyStage()
方法的逻辑比较清晰,同时也比较关键,可分为以下几个步骤:
- 首先检查外部传入的 lambda 表达式是否为 NULL,如是则说明使用方根本没有指定异步任务的执行逻辑,抛异常
- 调用
CompletableFuture
构造方法新建一个CompletableFuture
对象,该对象将用于方法返回,供外部持有引用- 使用新建的
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;
}
AsyncSupply
的结构如下所示,可以看到其继承于ForkJoinTask
并且实现了Runnable
接口,那么当其在ForkJoinPool 线程池
执行时会触发AsyncSupply#exec()
方法,在其他线程池执行时会触发AsyncSupply#run()
方法。 另外其内部比较关键的属性如下:
dep
:AsyncSupply 操作对象自身对应的CompletableFuture
对象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();
}
}
}
- 主线程将异步任务提交到线程池后继续执行
CompletableFuture#thenAcceptAsync()
方法,可以看到此处操作与步骤1类似,都是调用对应的stage 后缀方法
记录操作
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action) {
return uniAcceptStage(asyncPool, action);
}
CompletableFuture#uniAcceptStage()
方法的实现很清晰,关键点如下:
- 首先依然是检查代表异步任务逻辑的 lambda 表达式,如果为 NULL 说明没有需要处理的任务逻辑,抛异常
- 新生成本次操作的
CompletableFuture
对象- 检查传入的执行器对象是否为 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;
}
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;
}
......
}
CompletableFuture#push()
方法为本次操作的上一个CompletableFuture
对象调用,也就是说当前操作所依赖的前置操作的CompletableFuture
对象会尝试将当前操作压入栈中,具体做法如下:
- 检查上一个
CompletableFuture
对象的result
属性,如果为 NULL 说明上一个操作尚未完成,本次操作自然也无法执行,只能尝试将本次操作暂存到上一个操作对应的CompletableFuture
对象的无锁并发栈中。while 循环保证 CAS 插入失败可以不断检查上一个操作是否完成,从而决定是否重试插入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);
}
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);
}
CompletableFuture#uniAccept()
方法的逻辑有点绕,比较关键的点如下:
- 首先根据当前操作对象保存的上一个
CompletableFuture
对象的result
字段判断上一个操作是否完成,未完成直接返回false
结束- 如果上一个操作已经完成了,则检查当前操作对应的
CompletableFuture
对象的result
字段,判断自身是否已经执行完成- 自身未执行完成的话首先检查上一个操作是否是正常完成(任务异常结束或者返回空结果时,CompletableFuture 对象的 result 为 AltResult 对象)
- 接下来检查当前操作是否可以执行,此处主要根据传入的参数
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;
}
UniAccept#claim()
方法其实是父类实现的UniCompletion#claim()
,这个方法的实现必须要说明
该方法会 CAS 检查异步任务的执行状态,如果未执行则将其提交给线程池执行,如果已经执行则返回。因为在多线程环境下每个操作其实都有两个执行的触发点,此方法的目的主要是为了保证每个操作只能被执行一次
- 假设此时本文示例的第一个异步任务已经执行完成,而又还没有触发其操作栈中的其他操作,则新的操作对象在主线程中生成时可能被提交到线程池执行,也就是当前分析的这个流程
- 在线程池环境中,当前置操作完成后触发操作栈中操作执行时,本次操作的执行依然会从
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;
}
- 回到步骤5,
CompletableFuture#uniAcceptStage()
方法的执行到此结束,主线程中后续的CompletableFuture#thenRunAsync()
操作与CompletableFuture#thenAcceptAsync()
的处理流程大同小异,不再赘述
2.2.2 任务在线程池中的执行流程
AsyncSupply
异步任务被提交到了ForkJoinPool
线程池,则其执行时AsyncSupply#exec()
方法触发,可以看到AsyncSupply#exec()
方法其实主要逻辑是调用AsyncSupply#run()
方法。AsyncSupply#run()
方法的逻辑比较简单:
- 执行保存的 lambda 表达式,然后将执行结果通过 CAS 设置到其对应的 CompletableFuture 对象中
- 调用
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();
}
}
CompletableFuture#postComplete()
方法检查是否有下一个操作需要执行,如果需要便会触发该任务的执行,其比较关键的步骤如下:
- 如果 CompletableFuture 的操作栈不为空,则 CAS 从操作栈的栈顶取出待执行的操作
- 执行代码
h.tryFire(NESTED)
触发下一个操作的处理方法,执行业务逻辑,并返回一个 CompletableFuture 对象- 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;
}
}
}
- 示例代码中
h.tryFire(NESTED)
实际触发的是UniAccept#tryFire()
方法,但是需要注意此处入参为NESTED
,则其最终会在 2.2.1 节步骤10处阻断,并不会真正执行thenAcceptAsync
操作的任务逻辑,此部分不再赘述 - 在 2.2.1 节主线程的执行流程中,我们假设
thenAcceptAsync
操作对应的异步任务UniAccept
由主线程提交到了线程池,则可以知道当其执行时必然也以UniAccept#exec()
方法作为入口,实际上该方法为父类的实现Completion#exec()
public final void run() { tryFire(ASYNC); }
public final boolean exec() { tryFire(ASYNC); return true; }
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);
}
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;
}
- 回到步骤5,
CompletableFuture#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;
}