我们知道FutureTask实现了task异步执行,但对于执行结果的获取,如果异步执行还在进行中,那么线程只能get
阻塞等待,或者轮询isDone
,这两种方式都和我们开始实现异步的初衷相违背。所以就诞生了这个CompletableFuture,它的最大不同之处在于,通过提供回调函数的概念,把处理执行结果的过程也放到异步线程里去做。
JUC框架 系列文章目录
CompletableFuture实现了Future接口和CompletionStage接口,CompletionStage接口提供了很多异步回调的函数。
有两种方法可以创建CompletableFuture:
supplyAsync
。属于零输入,执行时机是马上执行。CompletableFuture对象.thenApply
。属于有输入,执行时机是调用对象的完成时机。 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对象是用户接触不到的,它用来驱动CompletableFuture对象。
abstract static class Completion extends ForkJoinTask<Void> implements Runnable, AsynchronousCompletionTask {...}
ForkJoinTask
,但也仅仅是为了套上ForkJoinTask
的壳子,因为CompletableFuture默认的线程池是ForkJoinPool.commonPool()
。Runnable
,这使得它也能被一个普通线程正常执行。tryFire
方法。 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。
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使用的,实现对 想获取执行结果的线程 的阻塞和唤醒的功能。
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。
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(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(d, f))
,run函数就会被另一个线程执行。当task被异步执行完毕后,会调用completeValue
或completeThrowable
来为result
成员赋值。
上图体现了supplyAsync()
的过程,对于调用者来说,只能接触到stage对象,并且调用者根本不知道stage对象何时能产生运行结果。对于实现来说,把task包装成一个AsyncSupply对象,另起线程执行task,执行完毕后为stage对象赋值运行结果。
注意,stage完成的标志,就是它的result
成员非null。
在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。具体的说:
thenApplyAsync
会传入一个Executor,因为它总是让 Executor线程池里面的线程 来执行后续stage的task。具体的说:
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 d = new CompletableFuture()
和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:
c.tryFire(SYNC)
中把接管的当前stage转交给e
执行。e
执行。e
为null:
e
是否为null,都有必要再尝试一下c.tryFire(SYNC)
,避免此时前一个stage已经完成的情况。c.tryFire(SYNC)
中也会执行类似d.uniApply(this, f, null)
,而且你会发现两种调用环境,uniApply
成员函数的this对象是一样的(当前stage),第一个实参是一样的(前一个stage),第二个实参也是同一个函数式接口对象,只有第三个实参不一样。在讲tryFire
之前,我们先看看tryFire
有几处调用:
uniApplyStage
中的同步调用,c.tryFire(SYNC)
。run
的d.postComplete()
中,会调用tryFire(NESTED)
。tryFire
的this对象都是我们分析过程提到的当前stage。并且,这说明tryFire
可能会有多线程的竞争问题,来看看tryFire
是怎么解决的。
run
的d.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)
。
这个函数的逻辑之前讲过简单的一版,即当第三个实参为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. c为null,这说明c还没有入栈,没有线程竞争。直接执行当前stage即f.apply(s)
//2. c不为null,这说明c已经入栈了,有线程竞争执行当前stage。
if (c != null && !c.claim())
//claim返回了false,说明当前线程不允许执行当前stage,直接返回
return false;
//claim返回了true,说明当前线程允许接下来执行当前stage
@SuppressWarnings("unchecked") S s = (S) r;
completeValue(f.apply(s));
} catch (Throwable ex) {
completeThrowable(ex);
}
}
//如果this即当前stage的result不为null,说当前stage已经执行完毕,那么直接返回true
return true;
}
if ((x = ((AltResult)r).ex) != null)
判断中,如果发现前一个stage执行完是因为抛出了异常,那么当前stage也不能正常执行了,直接把这个异常赋值给当前stage的result
成员。
break tryComplete
后,uniApply
函数会马上返回true,然后回到tryFire
函数,紧接着马上执行dep = null
(这代表当前stage已经执行完毕)。这样可以使得某种特殊时序下,执行前一个stage的线程不会通过tryFire
函数中的(d = dep) == null
的检查,进而直接返回null不去执行d.postFire(a, mode);
。总之,这是为了避免对同一个CompletableFuture对象调用它的成员函数postComplete
。dep = null
”不是原子性,两个线程的执行顺序也有可能穿插执行,有可能当前线程还没来得及“执行dep = null
”,执行前一个stage的线程就开始执行tryFire
函数了。if (c != null && !c.claim())
是用来保护函数式接口只被执行一次的。claim
函数返回true代表函数式接口接下来可以被当前线程同步执行。 final boolean claim() {
Executor e = executor;
//如果该Completion包含有Executor,那么此函数每次都会返回false。
// CAS保证函数式接口只被提交一次给Executor
//如果该Completion没有Executor,那么此函数第一次返回true,之后每次返回false。
// CAS保证函数式接口只被同步执行一次
if (compareAndSetForkJoinTaskTag((short)0, (short)1)) {
if (e == null)
return true;
executor = null; // disable
e.execute(this);
}
return false;
}
claim()
函数中处理掉了。经过CAS的保护,这个函数式接口能够保证只被执行一次(可能是同步执行、或者提交给Executor异步执行)。CompletableFuture#uniApply
的返回值含义:
tryFire
,将执行d.postFire(a, mode)
,因为执行完毕了当前stage的函数式接口,当前线程就得处理当前stage的后续任务。在tryFire
函数中,如果d.uniApply(a = src, fn, mode > 0 ? null : this)
返回了true,说明当前线程执行了当前stage的函数式接口(这说明没有提供线程池,当前线程同步执行了stage),自然接下来会去处理后续stage(d.postFire(a, mode)
)。
在提供了线程池的情况下,且前一个stage没有抛出异常正常执行完的情况下,tryFire
函数中的d.uniApply(a = src, fn, mode > 0 ? null : this)
必然会返回false,因为它把uniApply
对象提交给了线程池来执行。当前线程将不会去处理后续stage了(d.postFire(a, mode)
)。
提交给线程池后,因为uniApply
对象也是一个Runnable对象,它的run函数为:
public final void run() { tryFire(ASYNC); }
线程池将提供一个线程来执行tryFire(ASYNC)
,之后d.uniApply(a = src, fn, mode > 0 ? null : this)
将会直接执行当前stage的函数式接口,返回true后,再去处理后续stage(d.postFire(a, mode)
)。
UniApply
内部类对象的tryFire(NESTED)
。当前stage.uniApply()
。UniApply
内部类对象的claim
方法。claim
会把任务提交给线程池(e.execute(this)
),把保存的线程池清理掉(executor = null
),然后返回false。执行前一个stage的线程接着层层返回,最终tryFire(NESTED)
返回null。UniApply
内部类对象执行tryFire(ASYNC)
。当前stage.uniApply(a = src, fn, ASYNC > 0 ? null : this)
,第三个实参肯定为null。ASYNC为1.if (c != null && !c.claim())
不会执行,因为第三个参数为null。 public static void test() {
CompletableFuture<String> a = CompletableFuture.supplyAsync(() -> {
String supplyAsyncResult = " "+Thread.currentThread().getName()+" Hello world! ";
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(supplyAsyncResult);
return supplyAsyncResult;
});
a.thenApplyAsync(r -> { //添加后续任务
String thenApplyResult = Thread.currentThread().getName()+r + " thenApply1! ";
System.out.println(thenApplyResult);
return thenApplyResult;
});
a.thenApplyAsync(r -> { //添加后续任务
String thenApplyResult = Thread.currentThread().getName()+r + " thenApply2! ";
System.out.println(thenApplyResult);
return thenApplyResult;
});
try {
System.out.println(a.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! thenApply2!
ForkJoinPool.commonPool-worker-9 ForkJoinPool.commonPool-worker-9 Hello world! thenApply1!
ForkJoinPool.commonPool-worker-9 Hello world! finish!
*/
虽然上面讲到的例子为链式编程,但其实也可能变成树形结构,这也将解释为什么需要一个Completion stack
作为栈来存储后续任务。从上例可见,在a对象之后,我们添加了两个任务,这两个任务是并列。因为是栈结构,所以最后的任务反而先执行。
我们以树形结构来体现上例,由于两个后续任务是并列的,它们都被添加到a对象的stack中等待被执行。
当前stage被完成后,postComplete
函数就会被调用。比如AsyncSupply
内部类的run函数的最后;UniApply
内部类的tryFire
函数的最后。
//this对象总是那个 刚完成的当前stage
final void postComplete() {
CompletableFuture<?> f = this; Completion h;//f初始指向当前stage对象,也就是图中的a节点
//注意,遍历过程中,f可能不再指向this,而是树形结构下面层次的节点
//1. 如果当前f的stack不为null,那么短路后面
//2. 如果当前f的stack为null,且f是this(f != this不成立),说明整个树形结构已经被遍历完毕,退出循环
//3. 如果当前f的stack为null,且f不是this,那么让f恢复为this,再查看this的stack是否为null
// 1. 如果
while ((h = f.stack) != null ||
(f != this && (h = (f = this).stack) != null)) {
CompletableFuture<?> d; Completion t;
if (f.casStack(h, t = h.next)) { //使得f的栈顶指针下移
if (t != null) { //如果h是有后继的
if (f != this) { //如果f不是树形结构的root,就把f的后续任务压入root(this对象)的栈,直到全部压入
pushStack(h);
continue;
}
h.next = null; //如果h是有后继的,需要断开h的next指针
}
//Completion执行tryFire(NESTED)后,有两种情况:
//1. 以上图为例,可能会返回Completion对象的当前stage。因为当前线程在tryFire中执行了h的当前stage
// 所以f会更新为树形结构中的非root的节点
//2. 也有可能会返回null。因为当前线程在tryFire中没有执行h的当前stage,这说明h的当前stage的stack不需要当前线程来处理了。
// 所以要把f恢复为this
f = (d = h.tryFire(NESTED)) == null ? this : d;
}
}
}
如下图所示,对后续任务的处理可能会变成树形结构的遍历,一般做法是递归去处理,但这样可能会造成很深的递归。而对tryFire
的调用可能是这种过程:postComplete -> tryFire -> postFire -> postComplete
,这样是会造成递归调用的。
但是现在,该函数通过tryFire(NESTED)
的特殊处理,并且在遇到第二层次以下的后续任务时,先把后续任务放到树形结构中的第二层次,再来执行,从而解决了递归调用的问题。tryFire(NESTED)
的特殊处理是指,当参数为NESTED时,只处理这个Completion对象的当前stage,不处理当前stage的后续stage。
以上图为例,蓝色节点代表局部变量f
指向的节点。当a节点的stage完成,但还没执行postComplete
时,整个树形结构如上图。
执行完第一次循环后,如上图,此时刚完成了b节点的stage。
下一次循环中,由于局部变量f
并没有指向this,所以把f
的后续任务弄到第二层次去。上面的过程连续做几次,直到把f
的后续任务全弄没。
把局部变量f
的后续任务全弄没后,如上图。
下一次循环的检查条件将更新f
为根节点,接下来就像第一次调用postComplete
时,再依次处理第二层次的各个后续任务。
这种并发调用类似于ConcurrentHashMap的transfer函数,在transfer函数中,各个线程领取各自的连续哈希桶区间作为任务,领取成功后将transferIndex
前移。
postComplete也能安全的并发调用,因为每个线程领取任务也是通过f.casStack(h, t = h.next)
成功出栈节点来完成的,因为是CAS操作,所以两个线程不可能出栈同一个节点。
但是,这种并发调用其实很难发生,我这里只想到了一种case。以树形结构为例,假设现在有根节点线程(supplyAsync
产生的),树形结构的第二层次已经有很多节点了(已执行根节点.thenApply()
),现在还有一个正在执行的同步线程(正在执行根节点.thenApply()
)。
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>();
if (e != null || !d.uniApply(this, f, null)) {
UniApply<T,V> c = new UniApply<T,V>(e, d, this, f);
push(c);
//此时,根节点线程突然执行过程中抛出了异常。假设此时根节点线程已经执行了run函数的d.completeThrowable(ex)
//但还执行到d.postComplete()
c.tryFire(SYNC);
}
return d;
}
//假设根节点线程和同步线程一起执行到tryFire。注意,tryFire的this对象是同一个
final CompletableFuture<V> tryFire(int mode) {
CompletableFuture<V> d; CompletableFuture<T> a;
if ((d = dep) == null ||
!d.uniApply(a = src, fn, mode > 0 ? null : this))//两个线程一起执行到这里
return null;
dep = null; src = null; fn = null;
return d.postFire(a, mode);//两个线程一起执行到这里
}
final <S> boolean uniApply(CompletableFuture<S> a,
Function<? super S,? extends T> f,
UniApply<S,T> 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;
completeValue(f.apply(s));
} catch (Throwable ex) {
completeThrowable(ex);
}
}
return true;//两个线程一起执行到这里
}
在postFire
中有所不同。
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;
}
postComplete
,但mode是NESTED说明是上层函数postComplete
调用进来的,然后postFire返回 -> tryFire返回 -> postComplete继续执行
,继续执行的postComplete的this对象是根节点。a
的result不为null(根节点线程执行自身stage时抛出了异常嘛),所以会执行a.postComplete()
,这里参数a
是this对象的前一个stage,也就是例子中的根节点。在前面的分析可知,UniApply#tryFire
成功执行了这个UniApply
对象的当前stage后,将会调用当前stage的postFire
。
final CompletableFuture<T> postFire(CompletableFuture<?> a, int mode) {
//前一个stage的后续任务还没做完
if (a != null && a.stack != null) {
//1. mode为NESTED。说明就是postComplete调用过来的,那么只清理一下栈中无效节点即可。
//2. mode为SYNC或ASYNC,但前一个stage还没执行完。不知道何时发生,因为调用postFire的前提就是前一个stage已经执行完
if (mode < 0 || a.result == null)
a.cleanStack();
//3. mode为SYNC或ASYNC,但前一个stage已经执行完了。特殊时序可能发生的,那么帮忙完成前一个stage的的后续任务
else
a.postComplete();
}
//当前stage的后续任务还没做完
if (result != null && stack != null) {
if (mode < 0)//mode为NESTED。说明就是postComplete调用过来的.
return this;
else //mode为SYNC或ASYNC,那么调用postComplete
postComplete();
}
return null;
}
//UniCompletion内部类
final boolean isLive() { return dep != null; }
我们知道一个Completion
对象的当前stage如果已经完成,那么它的dep
成员肯定为null。
final void cleanStack() {
//遍历过程中,可能的结构为p -> q -> s, q为当前遍历节点
for (Completion p = null, q = stack; q != null;) {
Completion s = q.next;
if (q.isLive()) { //如果q是有效节点,更新q,用p保存旧q
p = q;
q = s;
}
//如果q不是有效节点
else if (p == null) { //如果q是栈顶,那么移动栈顶
casStack(q, s);
q = stack;
}
else { //如果q不是栈顶,那么使得p -> q -> s变成p -> s,达到移除q的目的
p.next = s;
//但这种移除成功的前提是q是一个有效节点
if (p.isLive()) //如果是有效的移除
q = s;
else { //如果不是有效的移除
p = null; // 需要从新开始整个循环
q = stack;
}
}
}
}
整个移除过程符合Treiber stack的要求,具体讲解请看FutureTask的removeWaiter函数实现–带图讲解,二者实现完全一样。
我们知道thenApplyAsync
有两个重载版本,一个的线程池是默认的ForkJoinPool.commonPool()
,另一个的线程池则是调用者提供的线程池。
一般的链式操作为supplyAsync() ⇒ thenApplyAsync()
,而Async提供的语义是异步执行,与当前线程肯定不是同一个线程。当前线程就是调用supplyAsync
和thenApplyAsync
的线程。
但注意,supplyAsync
和thenApplyAsync
这二者执行线程并不一定是同一个线程,这取决于线程池的选择。在claim
函数中可见,当发现提供有线程池时,当前线程只是将任务提交给了线程池后(e.execute(this)
)就马上返回了。
本章开始的例子,会发现这二者执行线程总是同一个。
thenApply
是一个CompletableFuture的成员方法,它依赖于前置stage的执行完成。而这样的方法还有很多。
0. thenApply(thenApplyAsync)
thenApply需要一个输入,有输出。
1. thenAccept(thenAcceptAsync)
两个函数都会调用到CompletableFuture#uniAcceptStage
,看起来只是函数字眼变了,另外是靠UniAccept
内部类来驱动的。
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;
}
观察整个过程你会发现,只有CompletableFuture#uniAccept
发生了实质的变化。
final <S> boolean uniAccept(CompletableFuture<S> a,
Consumer<? super S> f, UniAccept<S> c) {
...
try {
if (c != null && !c.claim())
return false;
@SuppressWarnings("unchecked") S s = (S) r;
f.accept(s);//执行完函数式接口后
completeNull();//当前stage的result被赋值为null的包装对象,代表当前stage结束
} catch (Throwable ex) {
completeThrowable(ex);
}
...
}
看来thenAccept方法需要一个输入,但没有输出。
2. thenRun(thenRunAsync)
直接看CompletableFuture#uniRun
。
final boolean uniRun(CompletableFuture<?> a, Runnable f, UniRun<?> c) {
Object r; Throwable x;
if (a == null || (r = a.result) == null || f == null)
return false;
if (result == null) {
if (r instanceof AltResult && (x = ((AltResult)r).ex) != null)
completeThrowable(x, r);
else
try {
if (c != null && !c.claim())
return false;
f.run(); //运行函数式接口时,连输入都不需要了
completeNull();
} catch (Throwable ex) {
completeThrowable(ex);
}
}
return true;
}
看来thenRun方法不需要输入,也没有输出。但也得等到依赖的stage完成后,才能执行当前stage。
2.5
你可能会想,为什么没有一个“不需要输入,但需要输出”的版本,其实这就是thenApply啦,因为你可以让你的Supplier
函数式接口直接忽略掉参数。
简单总结一下:
需要输入 | 是否输出 | |
---|---|---|
applyXXX | √ | √ |
acceptXXX | √ | × |
runXXX | × | × |
3. thenCombine(thenCombineAsync)
//this对象和o参数,是两个输入
private <U,V> CompletableFuture<V> biApplyStage(
Executor e, CompletionStage<U> o,
BiFunction<? super T,? super U,? extends V> f) {
CompletableFuture<U> b;
if (f == null || (b = o.toCompletableFuture()) == null)
throw new NullPointerException();
CompletableFuture<V> d = new CompletableFuture<V>();
if (e != null || !d.biApply(this, b, f, null)) {
BiApply<T,U,V> c = new BiApply<T,U,V>(e, d, this, b, f);
bipush(b, c);//入栈的时候,把Completion对象入栈到 两个输入的stack中
c.tryFire(SYNC);
}
return d;
}
//this对象和b参数,是两个输入
final void bipush(CompletableFuture<?> b, BiCompletion<?,?,?> c) {
if (c != null) {
Object r;
while ((r = result) == null && !tryPushStack(c))//先入栈到this对象里去
lazySetNext(c, null); // clear on failure
if (b != null && b != this && b.result == null) {
Completion q = (r != null) ? c : new CoCompletion(c);//CoCompletion只起到包装的作用,实际上所有实现都依赖于c
while (b.result == null && !b.tryPushStack(q))//再入栈到b参数里去
lazySetNext(q, null); // clear on failure
}
}
}
最大的区别在于,Completion对象对两个输入CompletableFuture,都要做入栈动作。
再看会tryFire
调用的CompletableFuture#biApply
。
final <R,S> boolean biApply(CompletableFuture<R> a, //需要有两个输入,所以有两个CompletableFuture参数
CompletableFuture<S> b,
BiFunction<? super R,? super S,? extends T> f,
BiApply<R,S,T> c) {
Object r, s; Throwable x;
if (a == null || (r = a.result) == null || //需要检查两个CompletableFuture是否已经都已经完成了
b == null || (s = b.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;
}
if (s instanceof AltResult) {
if ((x = ((AltResult)s).ex) != null) {
completeThrowable(x, s);
break tryComplete;
}
s = null;
}
try {
if (c != null && !c.claim())
return false;
@SuppressWarnings("unchecked") R rr = (R) r;
@SuppressWarnings("unchecked") S ss = (S) s;
completeValue(f.apply(rr, ss)); //函数式接口接受了两个参数
} catch (Throwable ex) {
completeThrowable(ex);
}
}
return true;
}
看来thenCombine方法需要两个输入,有输出。
4. thenAcceptBoth(thenAcceptBothAsync)
类似于thenCombine方法。thenAcceptBoth方法需要两个输入,但没有输出。
5. runAfterBoth(runAfterBothAsync)
类似于thenCombine方法。runAfterBoth方法不需要输入,也没有输出。但执行当前stage,需要等到输入的两个stage完成后才能执行。
6. applyToEither(applyToEitherAsync)
applyToEither与thenCombine类似,但thenCombine对两个输入stage的关系是&&
,而applyToEither对两个输入stage的关系是||
。即只要两个输入参数有一个执行完毕了,就可以执行当前stage的函数式接口,并且 两个输入参数谁先执行完毕,就使用谁的结果作为函数式接口的输入。
final <R,S extends R> boolean orApply(CompletableFuture<R> a,
CompletableFuture<S> b,
Function<? super R, ? extends T> f,
OrApply<R,S,T> c) {
...
if (a == null || b == null ||
((r = a.result) == null && (r = b.result) == null) || f == null)//a的结果优先使用
return false;
...
}
注意,执行a.applyToEither(b, fn)
时,如果两个输入stage都已经结束了,那么优先使用a这个输入stage的结果。
acceptEither和runAfterEither的分析类似,不再赘述。
7. handle(handleAsync)
虽然handle
的参数为BiFunction
类型,但不要以为它真正会接受到两个有效参数。两个参数一个代表前一个stage正常执行完,一个代表前一个stage抛出异常,所以两个参数只有一个是非null的。
handle
与前面函数的最大区别在于,它可以处理前一个stage抛出的异常。
final <S> boolean uniHandle(CompletableFuture<S> a,
BiFunction<? super S, Throwable, ? extends T> f,
UniHandle<S,T> c) {
...
try {
if (c != null && !c.claim())
return false;
if (r instanceof AltResult) {// r可能包装了null,也可能包装了异常
x = ((AltResult)r).ex; // x可能为null,也可能不是null
s = null; // s肯定是null
} else { // 前一个stage正常执行完成,且不是null的执行结果
x = null; //x肯定是null
@SuppressWarnings("unchecked") S ss = (S) r;
s = ss; // s肯定不是null
}
completeValue(f.apply(s, x));
} catch (Throwable ex) {
completeThrowable(ex);
}
}
...
}
所以,我们可以通过实参来判断到底是哪种情况:
s | x | 情况 |
---|---|---|
s非null | x为null | 正常执行完,且执行结果非null |
s为null | x为null | 正常执行完,且执行结果为null |
s为null | x非null | 执行过程中,抛出了异常 |
简单的说,可以通过x是否为null来判断是否抛出了异常。但不可能两个参数都是非null的。
handle需要输入,有输出。
whenComplete与handle类似,只是whenComplete没有输出。
8. exceptionally
首先注意这个函数没有Async版本,在初始化内部类UniExceptionally
对象时,给的Executor也是为null。
这个函数只处理前一个stage发现异常的情况,如果前一个stage正常执行完毕,那么直接将执行结果赋值给当前stage。
final boolean uniExceptionally(CompletableFuture<T> a,
Function<? super Throwable, ? extends T> f,
UniExceptionally<T> c) {
...
try {
if (r instanceof AltResult && (x = ((AltResult)r).ex) != null) { //如果前一个stage抛出了异常
if (c != null && !c.claim())
return false;
completeValue(f.apply(x)); // 处理异常
} else //如果前一个stage正常执行完
internalComplete(r); // 直接传递结果
} catch (Throwable ex) {
completeThrowable(ex);
}
}
...
}
9. thenCompose(thenComposeAsync)
这个方法有点特殊,你可以简单理解thenCompose为thenApplyAsync,总之,本文不准备讲解这俩函数。
前面提供了依赖CompletableFuture个数为1或2的方法,但现在总不能给3,4…的版本都写一遍吧,所以提供了一种allOf
方法,但它只能保证时机,并不能利用all任务们的执行结果。
allOf
使用了一种递归分治的方法,虽然任务可能有很多个,但最终它们都能拆分为1个或者2个。从最底层两两合并,弄成一个树形结构,最终这个树形结构的根节点就是我们想要的CompletableFuture,它们代表了所有任务都完成的时机。
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) {
return andTree(cfs, 0, cfs.length - 1);
}
从CompletableFuture
的返回类型可知,allOf
方法返回的CompletableFuture对象只是代表一种时机,这个时机就是cfs
里面的CompletableFuture都已经执行完毕。当我们在allOf
方法返回的CompletableFuture对象上添加后续任务时,能保证后续任务是这个特殊时机后才会触发的。
这种CompletableFuture对象它没有真正的task要执行。
static CompletableFuture<Void> andTree(CompletableFuture<?>[] cfs,
int lo, int hi) {
CompletableFuture<Void> d = new CompletableFuture<Void>();
if (lo > hi) // empty,不会执行到
d.result = NIL;
//执行到这里lo <= hi
else {
CompletableFuture<?> a, b;
int mid = (lo + hi) >>> 1;
//下面将对a和b进行赋值
//如果lo和hi相差1或者二者相等的话,mid会等于lo,则让a直接为lo索引处元素
//如果是其他情况,则递归调用
if ((a = (lo == mid ? cfs[lo] :
andTree(cfs, lo, mid))) == null ||
//如果lo等于hi,那么让b直接为a
//如果lo+1等于hi,那么让b为hi索引处元素
//如果是其他情况,则递归调用
(b = (lo == hi ? a : (hi == mid+1) ? cfs[hi] :
andTree(cfs, mid+1, hi))) == null)
throw new NullPointerException();
//无论前面有没有递归代用,执行到这里,a和b都已经赋值好了
if (!d.biRelay(a, b)) {//直接先尝试看看,a和b是否都已经执行完,返回false代表没有执行完
BiRelay<?,?> c = new BiRelay<>(d, a, b);//这个对象主要为了保存两个src和一个dep
a.bipush(b, c);//这里可能会将c先后入栈a和b
c.tryFire(SYNC);//入栈后再尝试一次,实际也是调用d.biRelay(a, b)
}
}
return d;
}
在入栈前,我们先直接检查a和b是否都已经执行完毕。这个函数很重要,在tryFire
中也会调用到它:
//this为dep对象,a和b为两个srcUI对象
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;
//如果两个src对象都已经执行完毕,则肯定返回true
if (result == null) {
//this对象的result对需要变成非null,原则是只要有一个src抛出了异常,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;
}
如果第一次尝试失败,则马上入栈。
来看一下a.bipush(b, c)
是实际是怎么入栈的。
final void bipush(CompletableFuture<?> b, BiCompletion<?,?,?> c) {
if (c != null) {
Object r;
while ((r = result) == null && !tryPushStack(c))//如果this对象还没完成,那么将c入栈到this
lazySetNext(c, null); // 如果入队失败,c的next指针清空,避免内存泄漏
//执行到这里,要么this已经完成,要么已经将c入栈this。总之,对this对象的完成时机已经有了保证
//有可能b也是this对象,这种情况不需要做什么。
if (b != null && b != this && b.result == null) {//如果b已经完成,那么不需要做入栈操作
//如果this已经完成,那么赋值c。因为只需要等待一个了
//如果this还没完成,那么赋值CoCompletion(c)
Completion q = (r != null) ? c : new CoCompletion(c);
while (b.result == null && !b.tryPushStack(q))
lazySetNext(q, null); // clear on failure
}
}
}
看起来给第一个src对象入栈的是c
,但第二个src对象入栈的是new CoCompletion(c)
。
入栈的对象是一种Completion对象,当它的前置stage执行完毕时,将调用它的tryFire
方法来完成它自身的stage。来看下BiRelay
即变量c
的tryFire
方法:
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);
}
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))//实际也是调用d.biRelay,来检查两个前置stage
return null;//如果两个没有都完成,则返回null
//如果两个都完成了
src = null; snd = null; dep = null;
return d.postFire(a, b, mode);//继续执行后续任务
}
}
来看看CoCompletion
,实际上它只是包装一下BiRelay
:
static final class CoCompletion extends Completion {
BiCompletion<?,?,?> base;
CoCompletion(BiCompletion<?,?,?> base) { this.base = base; }
final CompletableFuture<?> tryFire(int mode) {
BiCompletion<?,?,?> c; CompletableFuture<?> d;
if ((c = base) == null || (d = c.tryFire(mode)) == null)//实际上也是调用base的tryFire
return null;
base = null; // detach
return d;
}
final boolean isLive() {
BiCompletion<?,?,?> c;
return (c = base) != null && c.dep != null;
}
}
总之,当BiRelay
对象和CoCompletion
对象都分别入栈两个src对象后,先完成的src对象在执行后续任务(可能是BiRelay
对象或CoCompletion
对象)的tryFire
时会返回null,因为还差一方。后完成的src对象在执行后续任务的tryFire
时,才会执行成功。
假如allOf
给了五个依赖的stage,那么它的执行过程如上图。没有字母的节点都是属于“虚拟stage”,它们没有真正的函数式接口要执行。而根节点则反映了所有依赖的stage都完成了的时机。通过分治的手段,创建多个虚拟stage,最终得到了我们想要的那个根节点,因为根节点代表allOf
这个时机。
anyOf
与allOf
类似,我们直接看tryFire
会调用的那个关键方法吧。
//CompletableFuture#orRelay
final boolean orRelay(CompletableFuture<?> a, CompletableFuture<?> b) {
Object r;
if (a == null || b == null ||
((r = a.result) == null && (r = b.result) == null))//只要有一个src对象完成了,就不会返回false
return false;
if (result == null)
completeRelay(r);
return true;
}
果然只要有一个src对象完成了,orRelay
就不会返回false。
final boolean completeRelay(Object r) {
return UNSAFE.compareAndSwapObject(this, RESULT, null,
encodeRelay(r));
}
static Object encodeRelay(Object r) {
Throwable x;
return (((r instanceof AltResult) &&
(x = ((AltResult)r).ex) != null &&
!(x instanceof CompletionException)) ?
new AltResult(new CompletionException(x)) : r);
}
最后设置result
成员时,如果发现前置stage是抛出了异常,那么就把这个异常包装成一个CompletionException。如果已经是CompletionException了,则不用包装了。最后设置这个异常。当然,如果前置stage正常执行完,result
成员会设置为正常执行的结果。
这和allOf
的biRelay
的处理略有不同。
对于任何CompletableFuture对象,我们都可以调用get
函数来阻塞获得它的输出。对于CompletableFuture
对象,调用get
函数只是为了检测CompletableFuture
对象完成的时机。
public T get() throws InterruptedException, ExecutionException {
Object r;
return reportGet((r = result) == null ? waitingGet(true) : r);//如果this对象已经执行完成,直接reportGet
}
如果this对象还没执行完成,则调用waitingGet
,先自旋,再阻塞。当依赖的stage完成时,再将当前线程唤醒。
private Object waitingGet(boolean interruptible) {
Signaller q = null;
boolean queued = false;
int spins = -1;
Object r;
while ((r = result) == null) {
if (spins < 0)//开始自旋
spins = (Runtime.getRuntime().availableProcessors() > 1) ?
1 << 8 : 0; // Use brief spin-wait on multiprocessors
else if (spins > 0) {//每次自旋
if (ThreadLocalRandom.nextSecondarySeed() >= 0)//不一定每次自旋都会
--spins;
}
//如果自旋次数为0
else if (q == null)//但还没有生成Signaller
q = new Signaller(interruptible, 0L, 0L);
else if (!queued)//生成了但还没入栈
queued = tryPushStack(q);
//已经入栈了
else if (interruptible && q.interruptControl < 0) {//如果支持中断,且当前线程已经被中断
q.thread = null;//那么不能再等了,直接返回null
cleanStack();//帮忙依赖的stage,清理stack
return null;
}
else if (q.thread != null && result == null) {
try {
ForkJoinPool.managedBlock(q);//进行阻塞,这里面是也是循环阻塞的过程
} catch (InterruptedException ie) {
q.interruptControl = -1;
}
}
}
//执行到这里,说明阻塞已唤醒。可能是正常等到了依赖stage执行完,也可能是被中断了
if (q != null) {
q.thread = null;
if (q.interruptControl < 0) {//被中断了
if (interruptible)//如果本次调用支持被中断,那么返回null
r = null; // report interruption
else//如果本次调用不支持被中断,那么只是自我中断一下
Thread.currentThread().interrupt();
}
}
postComplete();//帮忙处理后续任务
return r;
}
主要通过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) {
...
}
else {//只会执行到这里
do {} while (!blocker.isReleasable() &&//其实block也是在调用isReleasable
!blocker.block());
}
}
可以看到整个执行过程是while (!blocker.isReleasable() && !blocker.block())
。
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: 可中断的, < 0: 已经被中断了
volatile Thread thread;
Signaller(boolean interruptible, long nanos, long deadline) {
this.thread = Thread.currentThread();
this.interruptControl = interruptible ? 1 : 0; //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);//tryFire唤醒即可
}
return null;
}
//返回true代表当前线程不需要阻塞了
public boolean isReleasable() {
if (thread == null)
return true;
if (Thread.interrupted()) {
int i = interruptControl;
interruptControl = -1;//只要被中断,不管之前的值是什么,都置为-1
if (i > 0)//如果支持中断,既然支持中断,那么不需要阻塞了
return true;
}
//虽然发现中断,但此对象不支持中断,那么也需要阻塞。这意味着会一直等到依赖stage执行完成
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();
}
final boolean isLive() { return thread != null; }
}
waitingGet
函数的参数interruptible
代表获得执行结果的当前线程,可以因为中断而终止阻塞。get
函数返回null有两种情况:
public T get() throws InterruptedException, ExecutionException {
Object r;
return reportGet((r = result) == null ? waitingGet(true) : r);
}
join
方法与get的唯一区别是,join
不支持中断,所以当前线程唤醒的唯一希望就是依赖的stage执行完毕。
public T get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
Object r;
long nanos = unit.toNanos(timeout);
return reportGet((r = result) == null ? timedGet(nanos) : r);
}
调用的是timedGet
,整个过程类似。我们只需要知道,超时版本是自带支持中断的功能。
private Object timedGet(long nanos) throws TimeoutException {
if (Thread.interrupted())//超时版本自带被中断的功能
return null;
if (nanos <= 0L) //不能给无意义的超时时间
throw new TimeoutException();
long d = System.nanoTime() + nanos;
Signaller q = new Signaller(true, nanos, d == 0L ? 1L : d); // 注意第一个参数为true,因为超时版本必定可以中断
boolean queued = false;
Object r;
// 这里没有像waitingGet一样进行自旋,因为限时中断就相当于自旋了
while ((r = result) == null) {
if (!queued)
queued = tryPushStack(q);
//1. q已经被中断了
//2. q已经超时了
else if (q.interruptControl < 0 || q.nanos <= 0L) {
q.thread = null;
cleanStack();
if (q.interruptControl < 0)//被中断
return null;
throw new TimeoutException();//已经超时
}
else if (q.thread != null && result == null) {
try {
ForkJoinPool.managedBlock(q);
} catch (InterruptedException ie) {
q.interruptControl = -1;
}
}
}
if (q.interruptControl < 0)//被中断了,就返回null
r = null;
q.thread = null;
postComplete();
return r;
}
cancel函数只是将CompletableFuture对象的result进行了赋值,但只有当CompletableFuture对象还没完成时。
public boolean cancel(boolean mayInterruptIfRunning) {
boolean cancelled = (result == null) &&//在stage还没有完成时,将this的result赋值为CancellationException的包装对象
internalComplete(new AltResult(new CancellationException()));//CAS可能失败
//1. 当前线程修改成功。
//2. 当前线程没修改成功,但其他线程cancel时修改成功。
//3. 当前线程没修改成功,是因为当前stage已完成
//既然此时result肯定不为null了,那么就需要帮忙处理后续
postComplete();//处理这个stage的后续任务
return cancelled || isCancelled();
}
public boolean isCancelled() {//考虑CAS失败,是因为别的线程调用了cancel,所以这里再检查一次
Object r;
return ((r = result) instanceof AltResult) &&
(((AltResult)r).ex instanceof CancellationException);
}
对比FutureTask的实现:
cancel
多次调用都会返回true。FutureTask的cancel
只有一次调用可以返回true。 public boolean complete(T value) {
boolean triggered = completeValue(value);
postComplete();
return triggered;
}
final boolean completeValue(T t) {
return UNSAFE.compareAndSwapObject(this, RESULT, null,
(t == null) ? NIL : t);
}
complete
一样有这个前提条件,只是赋值目标为null的包装对象。
public boolean completeExceptionally(Throwable ex) {
if (ex == null) throw new NullPointerException();
boolean triggered = internalComplete(new AltResult(ex));
postComplete();
return triggered;
}
final boolean internalComplete(Object r) { // CAS from null to r
return UNSAFE.compareAndSwapObject(this, RESULT, null, r);
}
complete
一样有这个前提条件,只是赋值目标为异常对象。
强行赋值,没有CAS操作。不管stage有没有执行完毕。
public void obtrudeValue(T value) {
result = (value == null) ? NIL : value;
postComplete();
}
public void obtrudeException(Throwable ex) {
if (ex == null) throw new NullPointerException();
result = new AltResult(ex);
postComplete();
}
FutureTask
对执行结果获取的痛点。CompletableFuture#postComplete
的实现。