前言
我们知道FutureTask实现了task异步执行,但对于执行结果的获取,如果异步执行还在进行中,那么线程只能get阻塞等待,或者轮询isDone,这两种方式都和我们开始实现异步的初衷相违背。所以就诞生了这个CompletableFuture,它的最大不同之处在于,通过提供回调函数的概念,把处理执行结果的过程也放到异步线程里去做。
基础设施
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被异步执行完毕后,会调用completeValue或completeThrowable来为result成员赋值。

上图体现了supplyAsync()的过程,对于调用者来说,只能接触到stage对象,并且调用者根本不知道stage对象何时能产生运行结果。对于实现来说,把task包装成一个AsyncSupply对象,另起线程执行task,执行完毕后为stage对象赋值运行结果。
注意,stage完成的标志,就是它的result成员非null。
thenApply(thenApplyAsync)
在supplyAsync直接返回了个CompletableFuture对象后,主线程在这个对象上调用thenApply或thenApplyAsync将后续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执行。
- 如果前一个stage已经执行完毕:当前线程在
- 如果
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的线程,在
run的d.postComplete()中,会调用tryFire(NESTED)。 - 上面两处,
tryFire的this对象都是我们分析过程提到的当前stage。并且,这说明tryFire可能会有多线程的竞争问题,来看看tryFire是怎么解决的。- 多线程竞争,比如当前线程入栈后,执行前一个stage的线程刚完事,正要触发后续stage(
run的d.postComplete()中)。
- 多线程竞争,比如当前线程入栈后,执行前一个stage的线程刚完事,正要触发后续stage(
//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

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





