在Java基础:CompletableFuture① 基础使用中我们介绍了CompletableFuture 的基础使用,本文来对其代码进行分析,由于个人能力所限并且文章编写时间太长(写了三四个月总被各种事情打断 ),文中所述可能存在理解不到位或者理解错误的情况,如有感谢指正。
CompletableFuture 代表一个执行阶段,一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发。
以下面的代码为例(为例方便讲解,这里把代码写的比较复杂)
public static void main(String[] args) {
final Supplier<String> startSupplier = () -> "start";
final Function<String, String> thenApplyFunction = s -> s + " -> thenApply";
final CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(startSupplier);
final CompletableFuture<String> thenApply = supplyAsync.thenApply(thenApplyFunction);
// 输出 : start -> thenApply
System.out.println(thenApply.join());
}
这段代码的实现很简单,如下:
我们下面来介绍一些 CompletableFuture 功能实现的一些关键类、属性, 方便我们下面展开代码分析时的理解。
想要知道 CompletableFuture 的具体实现原理,那么有两个属性我们绕不过去:
// 保存当前 CompletableFuture 的执行结果。CompletableFuture 通过 result 是否为空 判断当前 CompletableFuture 的操作有没有完成。
// 当 执行结果是 null 或者异常时,CompletableFuture 会将其包装成 AltResult 类型。
volatile Object result; // Either the result or boxed AltResult
// Completion 类型(Completion 是 CompletableFuture 的内部类),是当前 CompletableFuture 完成后要执行的操作堆栈。
volatile Completion stack; // Top of Treiber stack of dependent actions
除此之外 CompletableFuture 还存在一些枚举用来记录操作模式,如下:
// 同步模式 : 以同步模式来执行操作,表明调用线程来执行具体操作
static final int SYNC = 0;
// 异步模式 : 以异步模式来执行操作,表明当前操作已经交由线程池执行
static final int ASYNC = 1;
// 嵌套模式 :以嵌套模式来执行操作,将堆栈中的操作执行完。
static final int NESTED = -1;
CompletableFuture 为了保证操作的原子性,对于其中的属性都是通过 CAS 操作完成的。对于 CompletableFuture#result 和 CompletableFuture#stack,以及对 CompletableFuture.Completion#next 属性进行 CAS 操作。如下:
// CAS 准备操作
// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long RESULT;
private static final long STACK;
private static final long NEXT;
static {
try {
final sun.misc.Unsafe u;
UNSAFE = u = sun.misc.Unsafe.getUnsafe();
Class<?> k = CompletableFuture.class;
// 获取 CompletableFuture#result 地址
RESULT = u.objectFieldOffset(k.getDeclaredField("result"));
// 获取 CompletableFuture#stack地址
STACK = u.objectFieldOffset(k.getDeclaredField("stack"));
// 获取 Completion#next地址
NEXT = u.objectFieldOffset
(Completion.class.getDeclaredField("next"));
} catch (Exception x) {
throw new Error(x);
}
}
/** Completes with a non-exceptional result, unless already completed. */
// 通过 CAS 将 当前CompletableFuture 的result 属性设置为 t
final boolean completeValue(T t) {
return UNSAFE.compareAndSwapObject(this, RESULT, null,
(t == null) ? NIL : t);
}
/** Completes with an exceptional result, unless already completed. */
// 通过 CAS 将 当前CompletableFuture 的result 属性设置为 AltResult
final boolean completeThrowable(Throwable x) {
return UNSAFE.compareAndSwapObject(this, RESULT, null,
encodeThrowable(x));
}
/**
* Returns the encoding of the given (non-null) exception as a
* wrapped CompletionException unless it is one already.
*/
// 对异常进行包装
static AltResult encodeThrowable(Throwable x) {
return new AltResult((x instanceof CompletionException) ? x :
new CompletionException(x));
}
// 通过 CAS 更新 stack 的值,从 cmp 更新为 val
final boolean casStack(Completion cmp, Completion val) {
return UNSAFE.compareAndSwapObject(this, STACK, cmp, val);
}
/** Returns true if successfully pushed c onto stack. */
// 将 c 压入堆栈
final boolean tryPushStack(Completion c) {
Completion h = stack;
lazySetNext(c, h);
// 将 c 压入 栈中,替换 h
return UNSAFE.compareAndSwapObject(this, STACK, h, c);
}
// 赋值 c.next = next
static void lazySetNext(Completion c, Completion next) {
UNSAFE.putOrderedObject(c, NEXT, next);
}
CompletableFuture 完成操作依赖的关键类即 Completion。Completion 是 CompletableFuture 的内部类,也是执行具体操作的类,每个操作方法都具有一个实现类且都是 CompletableFuture 内部类,如 BiAccept、BiApply、UniAccept等。
对于一个 CompletableFuture 来说,CompletableFuture 内部的 Completion 是 当前环节要进行的操作。当 CompletableFuture 环节完成时(CompletableFuture#result !=null)并且后续操作不为空(Completion != null )时会对 CompletableFuture#result 进行对应的 Completion 处理。
这里我们直接来看 Completion 的实现, 其继承结构如下图:
可以看到 Completion 实现了 Runnable 接口,同时继承了 ForkJoinTask,那么对于 Runnable#run 和 ForkJoinTask#exec 方法也是有具体的实现的,如下:
abstract static class Completion extends ForkJoinTask<Void>
implements Runnable, AsynchronousCompletionTask {
// 记录下一步需要执行的操作
volatile Completion next; // Treiber stack link
// 如果触发,则执行完成操作,返回可能需要传播的依赖项(如果存在)。
abstract CompletableFuture<?> tryFire(int mode);
/** Returns true if possibly still triggerable. Used by cleanStack. */
// 如果可能仍然可以触发,则返回 true。由 cleanStack 使用
abstract boolean isLive();
// 异步模式执行 tryFire ,这里使用了final 修饰,表明该方法是终态的
public final void run() { tryFire(ASYNC); }
// 异步模式执行 tryFire ,这里使用了final 修饰,表明该方法是终态的
public final boolean exec() { tryFire(ASYNC); return true; }
// 获取和设置结果值
public final Void getRawResult() { return null; }
public final void setRawResult(Void v) {}
}
下面我们来对上面的方法做进一步的解释:
Completion#next : Completion 具有一个属性值 volatile Completion next
,由于 Completion 是堆栈结构,所以自然有一个 next 属性指向下一步的操作,如果有没有下一步操作,则为空。
Completion#tryFire :尝试完成当前操作,可能返回新的环节(CompletableFuture),根据调用场景的不同,分为同步、异步和嵌套模式执行, 同步调用并不代表一定是调用线程来执行操作,当发现操作需要异步执行时则会将操作丢入线程池中执行,线程池中会以异步模式执行操作。嵌套模式则会将当前环节的所有操作堆栈执行结束(清栈操作)。
Completion#isLive :代表当前Completion是否存活,在CompletableFuture#cleanStack 中会判断 Completion 是否存活,并清除死亡的 Completion。
Completion#run 和 Completion#exec : 实现都是直接调用 CompletableFuture.Completion#tryFire方法,参数为 CompletableFuture#ASYNC, 代表当前方法是异步执行。
Completion 有三个直接子类,分别为 CoCompletion、Signaller、UniCompletion。
CoCompletion :作为 BiCompletion 的静态代理,其内部并没有做什么逻辑,仅仅加了判空操作,具体处理都委托给了 BiCompletion 完成。
Signaller :作为一个信号量的存在,在CompletableFuture#join、CompletableFuture#get 方法中完成记录和释放等待线程。此类实现 ManagedBlocker 以避免在 ForkJoinPools 中堆积的阻塞操作时出现饥饿。
UniCompletion :我们使用的各种操作的父类,在Completion 基础上做了一定的扩展,是本文需要介绍的重点,如下:
/** A Completion with a source, dependent, and executor. */
@SuppressWarnings("serial")
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;
}
// 如果可以运行操作,则返回 true。仅在已知可触发时调用。使用 FJ 标记位来确保只有一个线程声明所有权。如果是异步的,则作为任务启动——稍后调用 tryFire 将运行操作。
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;
}
final boolean isLive() { return dep != null; }
}
可以看到相较于 Completion 类,UniCompletion 多了一些参数,如下:
额外需要注意的是 :UniCompletion 还有一个 重要的子类 BiCompletion,如下,与UniCompletion 相比,BiCompletion 多了一个来源。BiCompletion 是用于 CompletableFuture#applyToEither、CompletableFuture#runAfterBothAsync 这种函数聚合的操作,这种操作一般需要判断两个 CompletableFuture 的信息才能执行, 因此这里BiCompletion 相较于UniCompletion 多了一个第二来源。如下:
/** A Completion for an action with two sources */
@SuppressWarnings("serial")
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;
}
}
下面我们开始对 CompletableFuture 的代码进行分析,以下面的例子为例,我们来看具体的实现:
public static void main(String[] args) {
final Supplier<String> startSupplier = () -> "start";
final Function<String, String> thenApplyFunction = s -> s + " -> thenApply";
final CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(startSupplier);
final CompletableFuture<String> thenApply = supplyAsync.thenApply(thenApplyFunction);
// 输出 : start -> thenApply
System.out.println(thenApply.join());
}
这里很显然有三个方法需要我们来看:
下面我们来看具体的方法实现:
CompletableFuture#supplyAsync 按照注释的说法为 :【返回一个新的 CompletableFuture,它由在给定执行程序中运行的任务异步完成,其值通过调用给定供应商获得。】
简单来说:就是我们可以通过 CompletableFuture#supplyAsync 创建一个有入参有返回值的异步任务,我们也可用过 CompletableFuture#runAsync 创建一个无入参无返回值的异步任务。当这个任务完成时会判断是否有后置操作,如果有则处理后置操作。
下面我们具体来看其实现:
// 不指定线程池则使用默认的线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return asyncSupplyStage(asyncPool, supplier);
}
// CompletableFuture#supplyAsync 的重载方法,可以指定使用的线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,
Executor executor) {
return asyncSupplyStage(screenExecutor(executor), supplier);
}
static <U> CompletableFuture<U> asyncSupplyStage(Executor e,
Supplier<U> f) {
if (f == null) throw new NullPointerException();
// 创建一个 CompletableFuture,即演示代码中的 supplyAsync
CompletableFuture<U> d = new CompletableFuture<U>();
// 异步执行 AsyncSupply#run 方法
e.execute(new AsyncSupply<U>(d, f));
return d;
}
我们这里可以看到 CompletableFuture#supplyAsync 的逻辑就是向线程池中丢入一个 AsyncSupply 任务(AsyncSupply 是 CompletableFuture 的内部类),而 AsyncSupply 的实际作用就是执行 f 函数。下面我们来看CompletableFuture.AsyncSupply 的实现
由于我们将 AsyncSupply 交由线程池执行,所以这里会调用 AsyncSupply#run 来执行具体逻辑,如下:
static final class AsyncSupply<T> extends ForkJoinTask<Void>
implements Runnable, AsynchronousCompletionTask {
CompletableFuture<T> dep; Supplier<T> fn;
// dep : 新创建的 CompletableFuture,用作承载此次计算,作为返回值返回。演示代码中的 supplyAsync
// 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) {}
// 调用 run 方法
public final boolean exec() { run(); return true; }
// AsyncSupply#run 会判 d 环节是否已经结束(d.result 是否为空)
// 如果 d 环节尚未执行(d.result == null) 则执行 f.get() 并将结果通过 CAS赋值给 d.result
public void run() {
CompletableFuture<T> d; Supplier<T> f;
// 必要的参数校验 : dep 不为空 && fn 不为空才执行
if ((d = dep) != null && (f = fn) != null) {
// 将 dep 和 fn 置为空
dep = null; fn = null;
// 【注解1】d.result == null 说明 d 环节尚未被执行
if (d.result == null) {
try {
// 【注解2】通过 cas 将 f.get() 获取的值赋值给 d.result
d.completeValue(f.get());
} catch (Throwable ex) {
// 【注解2】若执行出现异常,将异常结果封装成 AltResult 赋值给 d.result
d.completeThrowable(ex);
}
}
// 【注解3】执行后置操作:如果还存在后置的操作,则触发并执行
d.postComplete();
}
}
}
我们按照注解顺序解释一下:
1. 【注解1】判断 d.result == null 才执行下面的逻辑,即 判断d 环节是否已经执行,如果尚未执行才会执行下面的逻辑。
2. 【注解2】执行供应商 f 的方法,并将结果赋值给新创建的 CompletableFuture#result,如果结果为 null,则包装成一个AltResult 赋值给新创建的 CompletableFuture#result,如果出现异常则将异常包装成 AltResult 赋值给新创建的 CompletableFuture#result。
/** The encoding of the null value. */
static final AltResult NIL = new AltResult(null);
/** Completes with a non-exceptional result, unless already completed. */
final boolean completeValue(T t) {
// CAS 替换 this.result 属性从 null 变为 t
// 这里需要注意,如果 t == null 则会被替换为 NIL,
return UNSAFE.compareAndSwapObject(this, RESULT, null,
(t == null) ? NIL : t);
}
/** Completes with an exceptional result, unless already completed. */
final boolean completeThrowable(Throwable x) {
// 这里会将异常包装成 AltResult
return UNSAFE.compareAndSwapObject(this, RESULT, null,
encodeThrowable(x));
}
/**
* Returns the encoding of the given (non-null) exception as a
* wrapped CompletionException unless it is one already.
*/
static AltResult encodeThrowable(Throwable x) {
return new AltResult((x instanceof CompletionException) ? x :
new CompletionException(x));
}
3. 【注解3】当 d 环节执行结束后,执行 CompletableFuture#postComplete触发后置操作,CompletableFuture#postComplete 的作用触发当前栈中的所有操作, 该方法会使用嵌套模式将栈中所有等待的操作依次出栈执行(即清栈操作)。该方法的实现过程较为复杂,我们下面来详细分析。
CompletableFuture#postComplete 方法官方的解释是 【弹出并尝试触发所有可到达的依赖项。仅在已知完成时调用】。
个人理解:当前环节完成后会判断 当前环节是否是否还存在后置操作(通过 CompletableFuture 的stack 属性是否为空),如果存在后置操作,则将后置操作出栈执行,根据具体的情况操作可能选择同步执行或者异步执行,直至将当前环节的操作栈清空为止。CompletableFuture#postComplete 的目的就是执行并清除当前 CompletableFuture 的所有操作栈。
该方法的实现比较复杂,具体实现如下:
// 弹出并尝试触发所有可到达的依赖项。仅在已知完成时调用
final void postComplete() {
// 在每一步,变量 f 都会保存当前依赖项以弹出和运行。它一次只沿着一条路径扩展,推动其他路径避免无限递归。
// 【注解1】初始化属性 f 记录当前环节 CompletableFuture
CompletableFuture<?> f = this; Completion h;
// 【注解2】 循环判断
// 如果f.stack 不为空(即对于 f 来说还有未完成的操作,f 可能是当前 CompletableFuture ,也可能是 子操作返回的 CompletableFuture )
// f != this 并且 this.stack 不为空( 即 f != this 即代表 f 可能是其子操作返回的新的CompletableFuture 已经完成了所有操作 但是 当前 CompletableFuture 还有 操作未完成)
while ((h = f.stack) != null ||
(f != this && (h = (f = this).stack) != null)) {
// d 作为 临时变量,临时过渡使用,t 会赋值为 h.next ,也是一个过渡属性
CompletableFuture<?> d; Completion t;
// 【注解3】h 出栈,将 h.stack 的值替换为 t
if (f.casStack(h, t = h.next)) {
// 如果 t 不为空 (则说明 除了 h 还有后置操作)
// 【注解 4】h 出栈后执行前的的一些操作
if (t != null) {
if (f != this) {
pushStack(h);
continue;
}
h.next = null; // detach
}
// 【注解5】触发 h 的操作,这里指定是嵌套模式。实际上在 CompletableFuture 中嵌套模式只会在这里调用
// 如果 tryFire 返回是空,则h 操作不需要返回新的 CompletableFuture,后续操作依旧依赖于 this
// 如果 tryFire 返回不为空,则 h 操作返回了新的 CompletableFuture,后续的操作则依赖于新的 CompletableFuture 执行,因为新的 CompletableFuture 可能还存在后置操作。
f = (d = h.tryFire(NESTED)) == null ? this : d;
}
}
}
这里的整个逻辑比较绕,我们按照注释顺序来一步一步分析:
1. 【注解1】:这一步声明了两个属性 ,f 初始化为 this,h初始化为 null 。
CompletableFuture<?> f = this; Completion h;
实际上在后面的循环中 f 表示的是当前 CompletableFuture ,这个可能是其子操作返回的CompletableFuture,不一定是 this。h 后面会赋值为 f.stack, 即 基于 f 的子操作。h 的操作可能会返回一个新的 CompletableFuture。
2. 【注解2】:这里来说明一下 while 循环进入条件 : f 具有子操作未处理 || f = this 并且 f 还具有子操作:
// 1. 条件1 :(h = f.stack) != null : f.stack 不为空说明 f 还有未完成的操作
// 2. 条件2 :(f != this && (h = (f = this).stack) != null)
while ((h = f.stack) != null ||
(f != this && (h = (f = this).stack) != null)) {
条件1 : 将 h 赋值为 f.stack, 并且 如果 h != null 即可满足,所以条件1 的判定条件是 f.stack != null 即 f 后续存在子操作,这里的 f 可能是 this 也可能是 h.tryFire(NESTED)
返回的新的 CompletableFuture。
条件2 :当到达这一步的判断,说明 f.stack = null,即对于 f 来说已经没有子操作了,这里也分为两个 且 的判断
f != this
: 即当前的 f 不是 当前 CompletableFuture 类,而是其子操作的 CompletableFuture 。如果 f.stack = null 并且 f = this 则说明当前环境的所有操作都执行结束了就没必要再执行了。所以这里首先要判断 f != this(h = (f = this).stack) != null)
:即 this.stack 不为空,到达这一步说明 f.stack = null 并且 f != this 则需要判断对于 this 来说是否还具有未执行的操作才有必要继续下去。3. 【注解3】:这里通过 CAS 操作将 f.stack 的栈顶元素(h)出栈。后面准备执行h操作
4. 【注解4】:这里是将 h出栈后还需要对一些元素做一些特殊处理,这里的目的是为了保证栈中每个操作都能执行,我们在后面的具体举例中可以看到其作用如下:
// h 出栈, t = h.next
// t != null 说明对于 f 来说,其子操作不止 h 一个
if (t != null) {
// f 不是this 时,将 h入栈到this 中,即将 h 的执行顺序提前
if (f != this) {
pushStack(h);
// 跳过本次循环
continue;
}
// h已经出栈,将 h.next 清空
h.next = null; // detach
}
5. 【注解5】:这一步是直接执行 h 操作,注意这里使用的是嵌套模式执行,只有在这里才会使用嵌套模式调用,可以简单理解当 CompletableFuture.Completion#tryFire 是嵌套模式中时一定是被 CompletableFuture#postComplete 调用,即一定处于清栈操作过程中。如下:
// h 执行 CompletableFuture.Completion#tryFire 方法,即尝试完成 h 操作
// 对于嵌套模式来说其返回值可能是 null 也可能为新的 CompletableFuture ,这里如果为空则将h 赋值为 this。否则f 赋值为新的 CompletableFuture 。
f = (d = h.tryFire(NESTED)) == null ? this : d;
下面我们会具体分析 CompletableFuture.Completion#tryFire 的实现,这里我们先简单分析两个情况:
h.tryFire(NESTED) 返回 null
:当前线程未获取到 h 操作的执行权 || h操作未执行结束 || h.dep 没有后置操作h.tryFire(NESTED) 返回不为null
:当前线程获取到了操作 h 的执行权,并且 h操作执行结束 且 h.dep 存在操作需要执行。上面的代码分析我都不知道怎么描述才能更好的理解,因此下面举一个简单的例子来理解CompletableFuture#postComplete 方法的整个执行过程:
我们用下面的代码来进行分析(我们的例子中并未使用其他操作,如 CompletableFuture#thenApply 等,因为其实现逻辑和 CompletableFuture#whenComplete 如出一辙,所以这里全部使用了CompletableFuture#whenComplete演示 ):
public static void main(String[] args) throws InterruptedException {
CompletableFuture<String> startCF = CompletableFuture.supplyAsync(() -> {
// 注解1 : 睡眠 2s,默认任务耗时, 让后置操作都入栈执行,用于演示
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("START");
return "start";
});
// 异步执行,保证操作入栈
CompletableFuture<String> a1 = startCF.whenCompleteAsync((s, t) -> System.out.println("A1"));
CompletableFuture<String> b1 = startCF.whenCompleteAsync((s, t) -> System.out.println("B1"));
CompletableFuture<String> bb1 = b1.whenCompleteAsync((s, t) -> System.out.println("BB1"));
CompletableFuture<String> bb2 = b1.whenCompleteAsync((s, t) -> System.out.println("BB2"));
// 睡眠 5s, 让所有的 CompletableFuture 执行结束
Thread.sleep(5000);
}
输出结果如下图:
下面我们来分析一下 CompletableFuture#supplyAsync 执行结束时(此时控制台输出了 START),CompletableFuture#supplyAsync 会调用 CompletableFuture.AsyncSupply#run 执行操作,当操作执行结束会调用 CompletableFuture#postComplete 方法,:
进入第四次循环,如下图
需要注意的是,这里的代码通过 Thread.sleep 模拟了 startCF 的执行过程睡眠了2s,从而导致 a1,b1,bb1,bb2 等环节需要等待 startCF 环节执行结束才能执行,所以 a1,b1,bb1,bb2 都会入栈等待后出栈执行,并且由于栈先入后出的特性导致执行顺序有些差异(我们下面会分析,入栈逻辑在下面,这段话要不要放下面)如果不入栈,则会按照顺序执行输出 如下
CompletableFuture#postComplete 会执行清栈操作。这里会在 while 循环中以嵌套模式 调用 CompletableFuture.Completion#tryFire 方法(实际上,也只有 在 CompletableFuture#postComplete 中会以嵌套模式调用 CompletableFuture.Completion#tryFire )。也就是说当 CompletableFuture.Completion#tryFire 的 mode = -1 (嵌套模式时)则说明其是在 CompletableFuture#postComplete 中被调用,即还处于 CompletableFuture#postComplete方法的堆栈执行环节。
这里还是以一开始的例子做解释,为了方便解释,这里我们还是把代码贴出来。
```java
public static void main(String[] args) {
final Supplier<String> startSupplier = () -> "start";
final Function<String, String> thenApplyFunction = s -> s + " -> thenApply";
final CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(startSupplier);
final CompletableFuture<String> thenApply = supplyAsync.thenApply(thenApplyFunction);
// 输出 : start -> thenApply
System.out.println(thenApply.join());
}
可以知道 supplyAsync.thenApply(thenApplyFunction);
表示当 supplyAsync 执行结束后执行操作thenApplyFunction。这里就需要考虑 :当代码执行到 supplyAsync.thenApply(thenApplyFunction);
时, supplyAsync 是否执行结束(由于 supplyAsync 是异步执行,所以存在未执行结束的可能),如果supplyAsync 执行结束,则CompletableFuture会尝试执行直接执行thenApplyFunction 操作,否则则将 thenApplyFunction封装成 CompletableFuture.Completion,等待 supplyAsync 执行结束后再将操作出栈执行操作。
下面我们具体来看整个方法的实现:
public <U> CompletableFuture<U> thenApply(
Function<? super T,? extends U> fn) {
// 调用 uniApplyStage 方法
return uniApplyStage(null, fn);
}
private <V> CompletableFuture<V> uniApplyStage(
Executor e, Function<? super T,? extends V> f) {
if (f == null) throw new NullPointerException();
// CompletableFuture#thenApply 执行后要返回的 CompletableFuture,即演示代码中的 thenApply
CompletableFuture<V> d = new CompletableFuture<V>();
// 【注解1】这里的this 是演示代码中的 supplyAsync ,是当前操作的前置环节,判断当前是否可以需要入栈
if (e != null || !d.uniApply(this, f, null)) {
// 【注解2】 创建 UniApply 实例 并入栈等待执行
UniApply<T,V> c = new UniApply<T,V>(e, d, this, f);
// 【注解3】将 c压入栈 (不一定会入栈)
push(c);
// 【注解4】 同步触发一次操作
c.tryFire(SYNC);
}
return d;
}
我们按照代码注解顺序解析:
1. 【注解1】 判断 thenApply 操作是否需要入栈,这里的条件有两条:
e != null
: e 不为空,说明当前任务需要交由线程池异步执行,当前线程不允许执行该操作。 可以通过 thenApplyAsync 方式执行异步执行。!d.uniApply(this, f, null)
: 到了这一步说明e 为空,则说明当前操作可以同步执行,尝试是否可以执行成功,如果成功则不需要再入栈。d.uniApply(this, f, null)
返回 false的场景有两个 :this 环节 尚未执行结束
|| 当前线程未获取到执行权
。而这里只可能是 this 环节尚未执行结束
, 因为这里的调用 !d.uniApply(this, f, null)
的第三个参数为null。(关于 CompletableFuture#uniApply 方法我们在下面详细介绍。)2. 【注解2】将 UniApply 实例压入栈。进入这里有两种情况:
e != null
: 即当前操作需要异步执行,则通过 UniApply 异步执行。需要注意异步执行也需要前置环节执行结束。e = null && !d.uniApply(this, f, null)
:即 e 为空 但是d.uniApply(this, f, null)
返回 false,即当前操作可以同步执行,但是 this 环节尚未执行结束,所以当前操作只能入栈等待前置环节结束后再执行。(关于 CompletableFuture#uniApply 方法我们在下面详细介绍。)3. 【注解3】:将操作c 压入栈。
// 将c压入栈
/** Pushes the given completion (if it exists) unless done. */
final void push(UniCompletion<?,?> c) {
// 非空判断
if (c != null) {
// 如果 当前 CompletableFuture 尚未执行结束 (result == null),则尝试入栈
// 如果 入栈失败 !tryPushStack(c) = true, 则进入循环,一直到 c 成功入栈。
while (result == null && !tryPushStack(c))
lazySetNext(c, null); // clear on failure
}
}
这里需要注意
执行完push 方式,操作 c 并不一定会入栈。如果 this 环节执行结束 (this.result != null),则 c 不会再入栈。我们在上面已经知道在这里入栈的条件是 当前操作需要异步执行 || 当前操作可以同步执行但this 环节尚未执行结束
,并且即使是异步执行,也需要等待this 环节执行结束才能继续执行,那么我们可以知道操作 c 执行的必要条件就是this环节执行结束。而如果 result != null 时 则说明 this 环节已经结束,所以操作执行的前提其条件已经满足,就不必再强制入栈等待,直接通过 c.tryFire(SYNC)
触发当前操作就可以。
这两个入栈操作 (result == null
和 !tryPushStack(c)
)的判断过程并非是原子性的,在并发情况下可能会出现如下情况:
线程 A :判断 result == null
线程 B :完成 当前环节 (this) 的操作,赋值 result 并执行CompletableFuture#postFire 完成环节后置处理(检查当前环节未执行的堆栈执行等), 此时 result != null。
线程 A :执行 !tryPushStack(c) 将 c 入栈到this.stack 中
这里可以发现,如果按照上面的执行步骤,那么 c 操作将永远无法触发,因为 this 环节的清栈检查操作在线程B已经完成了。这里就会认为 this 的所有操作都已经完成,而不会再进行出栈执行操作。所以在后面会使用同步模式尝试触发一下操作(注解4的内容)
4. 【注解4】以同步模式尝试触发一次当前操作,因为入栈结束后,前置操作可能已经执行结束了,所以这里再一次尝试触发操作。除此之外还有上面说的特殊情况原因,防止c操作由于并发原因永远无法执行
CompletableFuture#postComplete
方法判断是否还有后置操作(通过 栈是否为空判断),而当我们执行到 615 或者 616 行,还未入栈时,此时前置操作执行结束,发现没有后置操作(栈为空)则会完成整个调用过程,从而可能错过执行当前操作。而在 617 行,即入栈成功后,再尝试触发当前操作,可以避免该情况的产生。下面我们来看一看 CompletableFuture#thenApply 方法中具体的几个方法,如下:
CompletableFuture#uniApply 的调用场景只有两处:
注 :CompletableFuture#uniApply 的第三个入参 UniApply
的作用的目的是为了保证操作执行的原子性,因为在某些场景下不需要线程竞争的情况下就为 null。 c
下面我们来看具体的 CompletableFuture#uniApply 实现:
// a :当前的 CompletableFuture 实例,当 a 执行结束后才能执行本次操作。这里是演示代码中的 supplyAsync。即 : 当 supplyAsync 执行结束后才能执行 thenApplyFunction
// f :本次将要执行的操作,即 thenApplyFunction
// c :UniApply 实例,当当前操作尚未入栈时为空,如果操作是从栈中弹出执行,则不为空
final <S> boolean uniApply(CompletableFuture<S> a,
Function<? super S,? extends T> f,
UniApply<S,T> c) {
Object r; Throwable x;
// 【注解1】通过 a.result 是否为空判断前一个环节是否已经执行结束
if (a == null || (r = a.result) == null || f == null)
return false;
tryComplete: if (result == null) {
// 【注解2】 如果上游执行出现了异常则对异常处理
if (r instanceof AltResult) {
// 如果上游执行出现了异常(出现异常会包装成 AltResult)
// ex 不为空说明上游出现了异常
if ((x = ((AltResult)r).ex) != null) {
// 将异常信息包装成 result
completeThrowable(x, r);
break tryComplete;
}
r = null;
}
try {
// 【注解3】 c 不为空 && 通过 CAS 没有到了任务执行所有权,直接返回false, 代表当前任务当前无法执行
if (c != null && !c.claim())
return false;
@SuppressWarnings("unchecked") S s = (S) r;
// 【注解4】执行当前任务,将结果赋值给 result
completeValue(f.apply(s));
} catch (Throwable ex) {
// 执行出现异常,包装异常信息
completeThrowable(ex);
}
}
// 当前任务执行成功
return true;
}
CompletableFuture#uniApply 的简单来说:
我们按照注解顺序解析:
1. 【注解1】条件校验,如果前一个环节已经完成(a.result != null) 才会往下执行,否则直接返回false(前一个环节都未执行结束,自然无法执行当前环节)
2. 【注解2】如果上游的结果类型是 AltResult,那么要么是上游执行抛出了异常,要么是上游结果为 null ,这两种情况都会将reuslt 封装成 AltResult 类型,这里对异常结果进行处理,传递异常结果
3. 【注解3】c != null && !c.claim()
的判断条件要成立需要 :当前操作是从栈中弹出或者执行过入栈操作(否则c 应该为空) && ( 当前线程没有获取到执行资格 || 当前操作需要异步执行),具体如下:
c != null
:上面我们说了 c != null 是在同步模式或者嵌套模式调用时才会成立。当 c != null 则说明 c 是执行过入栈的操作(但并非一定入栈了),则可能会存在多个线程竞争执行的情况。
!c.claim()
:代码走到这一步则说明 c != null,该方法的作用是判断当前线程是否有资格执行当前操作:首先通过 CAS确定当前操作由当前线程锁定处理(并非是当前线程执行),随后判断当前操作是异步执行还是同步执行,如果是同步执行,则返回true(因为CAS 操作已经确保当前操作由当前线程处理,而操作如果是同步执行,则由当前线程执行),如果是异步执行,则交由线程池执行,并返回false (因为c.claim() 方法 返回值是当前线程是否可以执行操作,如果是异步执行,则是交由线程池执行,所以这里判断如果是异步执行则直接交由线程池执行后直接返回false)。其实现具体如下:
// 返回 true的条件有下面两条:
// 1. ompareAndSetForkJoinTaskTag((short)0, (short)1) = true : 当前线程获取到了处理当前操作的资格(并非执行资格,如果是异步执行,则交由线程池执行)
// 2. e == null : 任务可以同步执行
final boolean claim() {
Executor e = executor;
// CAS 标志 当前操作由当前线程来处理(并非一定由由当前线程执行)
if (compareAndSetForkJoinTaskTag((short)0, (short)1)) {
// e = null 说明任务可以同步执行,并且当前线程已经通过 CAS 占据了该任务,所以直接返回ture, 表明当前线程可以执行当前操作
if (e == null)
return true;
// 走到这里说明线程池不为空,则当前任务需要异步执行(通过指定的线程池执行)
executor = null; // disable
// 交由线程池执行
e.execute(this);
}
// 走到这里说明任务交由了线程池执行,当前线程并未获取执行任务的权限,返回false
return false;
}
4. 【注解4】走到这里说明了当前任务是可以同步执行的,,并且要么当前操作未入栈(无线程争夺) || 当前操作入栈(可能存在线程争夺)但当前线程获取到了执行的资格。执行当前操作,并将结果通过 CAS 保存到 this.result 中
CompletableFuture#push 操作是将 CompletableFuture.UniApply 入栈,其关键逻辑我们在上面已经解释过了,这里不再赘述:
// 将c压入栈,直到成功为止
/** Pushes the given completion (if it exists) unless done. */
final void push(UniCompletion<?,?> c) {
// 非空判断
if (c != null) {
// 如果 当前 CompletableFuture 尚未执行结束 (result == null),则尝试入栈
// 如果 入栈失败 !tryPushStack(c) = true, 则进入循环,一直到 c 成功入栈。
while (result == null && !tryPushStack(c))
lazySetNext(c, null); // clear on failure
}
}
/** 如果成功将 c 压入堆栈,则返回 true。 */
final boolean tryPushStack(Completion c) {
Completion h = stack;
// CAS 使得 c.next = h = stack
lazySetNext(c, h);
// 将 this.stack 从 h 替换为 c, 完成 c 操作入栈
return UNSAFE.compareAndSwapObject(this, STACK, h, c);
}
static void lazySetNext(Completion c, Completion next) {
// CAS 使得 c.next = h
UNSAFE.putOrderedObject(c, NEXT, next);
}
CompletableFuture.UniApply#tryFire 的实现如下,目的是尝试执行当前操作。:
static final class UniApply<T,V> extends UniCompletion<T,V> {
Function<? super T,? extends V> fn;
// executor : 指定的线程池
// dep : 承载本次执行的 CompletableFuture,是演示代码中的 thenApply
// src : 本次执行的上游操作, 演示代码中的 supplyAsync
// fn : 要执行的具体操作
UniApply(Executor executor, CompletableFuture<V> dep,
CompletableFuture<T> src,
Function<? super T,? extends V> fn) {
super(executor, dep, src); this.fn = fn;
}
// mode :执行模式 同步(0)、异步(1)、嵌套(-1)。当前调用场景传入的是 0
final CompletableFuture<V> tryFire(int mode) {
CompletableFuture<V> d; CompletableFuture<T> a;
// 【注解1】尝试再次执行是否可以成功,执行失败直接返回 null
if ((d = dep) == null ||
!d.uniApply(a = src, fn, mode > 0 ? null : this))
return null;
// 【注解2】
dep = null; src = null; fn = null;
// 【注解3】操作执行成功才会调用到这里 ,a 为上游的 CompletableFuture ,演示代码中的 supplyAsync
return d.postFire(a, mode);
}
}
下面我们按照注解顺序解释:
1. 【注解1】:判断如果当前操作是否被执行过了(dep == null) || 当前线程无法处理该操作
(d = dep) == null
:这个判断的作用是判断当前操作是否已经被执行过,如果执行过, dep 会被置为null ,但这个过程并非是原子性的,所以并不能保证线程安全。(注解2的部分)!d.uniApply(a = src, fn, mode > 0 ? null : this)
: 该方法我们在上面解析过了,如果当前线程获得了执行当前操作的资格则返回 true。这里需要注意的是 当 mode > 0 时 会传入 null ,否则传入this。 关于CompletableFuture 的执行模式,我们本文开始提到过,如下: // 同步模式
static final int SYNC = 0;
// 异步模式
static final int ASYNC = 1;
// 嵌套模式
static final int NESTED = -1;
当 mode = 0 或 1 时都需要传入 this,因为无论是同步还是嵌套模式都需要保证自己的线程能获取处理当前操作的资格,因为同步和嵌套模式下的调用都可能出现多线程的并发问题。而如果当前操作是同步执行,则当前线程获取到处理权后直接处理,如果当前操作需要异步执行,则将其投递到线程池中支持。而当线程池中的线程触发当前操作时是通过 CompletableFuture.Completion#run 或 CompletableFuture.Completion#exec 方法执行,这两个方法传入的mode = 1, 所以这里实际上只有 ( 异步执行) 时mode = 1 才会传入null。而如果是异步执行 则说明当前操作交由线程池执行,则说明在此之前已经有一个线程获取到了处理该操作的资格并把该操作交由线程池执行,因此已经不存在其他线程与当前线程竞争,也没必要去争取执行权了。这里也需要注意:(d = dep) == null
和 !d.uniApply(a = src, fn, mode > 0 ? null : this)
的判断也并非是原子性的,可能存在多个线程同时执行这个操作,真正原子性的保证是在 d.uniApply(a = src, fn, mode > 0 ? null : this)
中的 (c != null && !c.claim()
。
2. 【注解2】走到这一步说明当前操作已经被当前线程执行结束,所以清空一些属性值,表明当前操作已经死亡(执行结束)。需要注意 CompletableFuture.UniCompletion#isLive 判断操作是否存活的依据就是 dep != null 。CompletableFuture.UniCompletion#isLive 方法会在 CompletableFuture#cleanStack 中用于判断 操作是否存活,不再存活的操作将会被移出栈。
3. 【注解3】当前操作执行结束后需要一些后置操作,根据模式的不同执行不同的逻辑。下面我们详细来看。
CompletableFuture#postFire 是操作执行结束的后置处理,根据模式的不同会执行不同的逻辑,如下:
下面我们来看具体代码:
// tryFire成功后由依赖进行后处理。尝试清理源 a 的堆栈,然后运行 postComplete 或将其返回给调用者,具体取决于模式
final CompletableFuture<T> postFire(CompletableFuture<?> a, int mode) {
// 【注解1】前置环节还有操作未完成,特殊的场景会出现下面细说
if (a != null && a.stack != null) {
// 【注解2】根据模式不同选择不同的处理方式 (a.result 必不为空,前面已经判断不为空才会执行当前操作之后才会执行到此)
// 如果是嵌套模式调用,则清除已完成的栈
// 如果是同步或异步模式则触发
if (mode < 0 || a.result == null)
// 清除已经完成的栈任务
a.cleanStack();
else
// 尝试再次触发a.stack 任务
a.postComplete();
}
// 【注解3】
if (result != null && stack != null) {
// 【注解4】 根据模式不同选择不同的处理方式
if (mode < 0)
return this;
else
// 尝试触发this.stack 任务
postComplete();
}
return null;
}
我们在上面已经介绍过 CompletableFuture#postComplete 方法的作用:CompletableFuture#postComplete 会将 CompletableFuture 的所有操作出栈执行结束。
我们按照代码注解顺序来分析:
1. 【注解1】当前置环节的前置环节还有操作未完成时会进入该分支,在并发的情况下会触发。
个人理解是通过多线程执行来加快栈处理的过程,我们用这个简单的例子来解释:
public static void main(String[] args) throws InterruptedException {
// 第1行
CompletableFuture<String> startCF = CompletableFuture.supplyAsync(() -> "start");
// 第2行
CompletableFuture<String> a1 = startCF.whenCompleteAsync((s, t) -> System.out.println("A1"));
// 第3行
CompletableFuture<String> b1 = startCF.whenCompleteAsync((s, t) -> System.out.println("B1"));
// 第4行
CompletableFuture<String> c1 = startCF.whenCompleteAsync((s, t) -> System.out.println("C1"));
// 第5行
CompletableFuture.allOf(a1, b1, c1);
}
a != null && a.stack != null
成立,因为 a 栈中还有一个 a1 未执行,则当前线程也会执行 a.postComplete() 协助清栈操作。这样就会存在主线程和 执行 c1 操作的线程同时执行 a.postComplete() 来完成清栈操作,加快清栈效率。
注: 上面的代码是为了方便解释,下面的代码可以确实复现这种情况:
public static void main(String[] args) throws InterruptedException {
CompletableFuture<String> startCF = CompletableFuture.supplyAsync(() -> {
try {
// 睡眠2s确保有足够的操作入栈,以进入并发情况
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("START");
return "start";
});
// 模拟高并发,多个操作一直入栈
while (true) {
CompletableFuture<String> a1 = startCF.whenCompleteAsync((s, t) -> System.out.println("A1"));
}
}
2. 【注解2】这里会根据模式的不同执行不同的逻辑,嵌套模式清理一下无用堆栈即可,异步或同步模式则需要执行CompletableFuture#postComplete 唤醒嵌套模式,为了避免前置环节还有未执行的操作。
前面我们提到这里可能会有多个线程同时执行,
a.cleanStack();
。我们上面提到过如果 CompletableFuture.Completion#tryFire 处于嵌套模式,则必定是被CompletableFuture#postComplete 调用执行,那么当前仍处于执行 CompletableFuture 堆栈执行过程,那么即是 a.stack != null
(a 的存在未执行的操作)之后也会被执行,并且由于可能有多个线程同时执行,所以这里清理下堆栈,以保证多线程执行时不会执行到已经处理过的操作。3. 【注解3】是判断当前环节是否执行结束( result != null )但存在未执行操作(stack != null)
4. 【注解4】在满足注解3 的要求下,如果是嵌套模式则直接返回 this(嵌套模式只会在 CompletableFuture#postComplete 中调用,这里返回this 是为了执行 this 的堆栈操作,完成子环节的清栈),同步或者异步模式则调用CompletableFuture#postComplete来执行堆栈中的操作。这里要结合 CompletableFuture#postComplete 方法一起看,是执行清栈操作。
CompletableFuture#join 和 CompletableFuture#get 的作用相同,都是阻塞获取 CompletableFuture 的执行结果,基本实现和调用方法也类似,不同的是 CompletableFuture#get 会抛出编译时异常,而 CompletableFuture#join 会将异常封装成运行时异常,因此下面我们只看 CompletableFuture#join 的实现 :
// 完成时返回结果值,如果异常完成则抛出(未经检查的)异常。为了更好地符合通用函数形式的使用,如果完成此 CompletableFuture 所涉及的计算引发异常,则此方法将引发(未经检查的) CompletionException ,并将底层异常作为其原因。
public T join() {
Object r;
// 如果 result 不为空直接返回,为空则调用 waitingGet(false) 等待
return reportJoin((r = result) == null ? waitingGet(false) : r);
}
// 解码结果以返回结果或抛出未经检查的异常
private static <T> T reportJoin(Object r) {
// 如果结果类型AltResult,则要么执行结果是 null,要么执行过程出现异常
if (r instanceof AltResult) {
Throwable x;
// 没出现异常,则返回null
if ((x = ((AltResult)r).ex) == null)
return null;
// 抛出异常
if (x instanceof CancellationException)
throw (CancellationException)x;
if (x instanceof CompletionException)
throw (CompletionException)x;
throw new CompletionException(x);
}
@SuppressWarnings("unchecked") T t = (T) r;
return t;
}
可以看到具体的实现逻辑都在 CompletableFuture#waitingGet 中。
CompletableFuture#waitingGet 实现如下:
// 等待后返回原始结果,如果可中断且被中断,则返回 null。
private Object waitingGet(boolean interruptible) {
Signaller q = null;
boolean queued = false;
int spins = -1;
Object r;
// 【注解1】当前环节未完成(result = null ) 则一直 while
while ((r = result) == null) {
// 【注解2】 : 自旋等待
//spins 默认 -1: 所以这里必然进入,判断是否是多核
if (spins < 0)
// 给 spins 赋值。 spins = 多核 ? 256 : 0
// 在多处理器上使用短暂的自旋等待
spins = (Runtime.getRuntime().availableProcessors() > 1) ?
1 << 8 : 0; // Use brief spin-wait on multiprocessors
// 当 spins > 0 (多核场景) 进入
else if (spins > 0) {
// 自旋等待
if (ThreadLocalRandom.nextSecondarySeed() >= 0)
--spins;
}
// 【注解3】 q 初始化并且将q 入栈到 this
// 对 q 进行初始化
else if (q == null)
// q 默认为空 所以这里会初始化
q = new Signaller(interruptible, 0L, 0L);
// 如果 q 没入栈,则入栈
else if (!queued)
// 尝试将q 入栈
queued = tryPushStack(q);
// 【注解4】如果当前任务可终止 && 当前线程已经被终止
else if (interruptible && q.interruptControl < 0) {
// 清理线程,方便gc,同时宣告 q 操作已经死亡
q.thread = null;
// 清理堆栈
cleanStack();
// 返回 null
return null;
}
// 【注解5】当前线程未被终止 && 当前环节还未结束
else if (q.thread != null && result == null) {
try {
// 通过 ForkJoinPool.managedBlock 来确保线程没有被阻塞
ForkJoinPool.managedBlock(q);
} catch (InterruptedException ie) {
// 出现异常,置为 false
q.interruptControl = -1;
}
}
}
// 【注解6】 this 环节结束,执行收尾工作
if (q != null) {
q.thread = null;
if (q.interruptControl < 0) {
if (interruptible)
r = null; // report interruption
else
// 修改当前线程中断标志位,告知线程被终止
// 如果当前线程处于阻塞状态则退出阻塞状态并抛出 InterruptedException 异常
// 如果当前线程非阻塞状态,则修改当前线程中断标志位为 true,线程会继续执行
Thread.currentThread().interrupt();
}
}
// 进行一次清栈操作
postComplete();
// 返回结果值
return r;
}
1.【注解1】:进入 while 循环的条件是 result = null。即当前环节尚未执行结束。同时我们可以知道,执行具体的线程和调用 CompletableFuture#join 必然不是一个线程。
如下:
a1.join();
时,a1 环节已经执行结束 ,result != null。b1.join();
时,b1 环境不一定已经结束,所以 reuslt 存在为空的可能。 public static void main(String[] args) {
CompletableFuture<String> startCF = CompletableFuture.supplyAsync(() -> "start");
CompletableFuture<String> a1 = startCF.whenComplete((s, t) -> System.out.println("A1"));
a1.join();
CompletableFuture<String> b1 = startCF.whenCompleteAsync((s, t) -> System.out.println("B1"));
b1.join();
}
2.【注解2】:如果是多核,则进行自旋等待。加入这个自旋,是为了稍晚一点执行后续逻辑中的 park 代码,这个稍重一点的操作。
2.【注解3】:初始化 q, 并将 q 入栈到 this中。这里 q 的类型是 CompletableFuture.Signaller,关于这个类我们在下面会详细介绍,Signaller 的作用是和 ForkJoinPool.managedBlock(q);
联合起来判断执行操作的线程是否被阻塞,以及是否需要创建补偿线程。
4.【注解4】:想要进入这个分支有满足 interruptible && q.interruptControl < 0
条件,进入这个分支说明当前任务允许终止并且当前线程的任务被终止了。
interruptible = true
:说明当前任务允许被终止,该值是 CompletableFuture#waitingGet 的入参,在CompletableFuture#join 中默认传入 false, 在 CompletableFuture#join 中传入 trueq.interruptControl < 0
:当前线程已经被执行过终止操作。而进入此分支后会执行如下代码来完善终止操作:
// 清空线程,q 是否存活的判断依据就是 q.thread 是否为 null
q.thread = null;
// 清除 this 中的死亡堆栈,这里一定会把 q 清除掉,因为 q 已经死亡
cleanStack();
// 返回null,,因为任务被终止
return null;
5.【注解5】:当 q.thread != null && result == null
(q操作未死亡 && this 环节还未执行结束)时进入此分支,到这里才真正算是对正常场景的处理:通过 ForkJoinPool#managedBlock 来保证 cpu 吞吐量。
这部分代码如下:
try {
ForkJoinPool.managedBlock(q);
} catch (InterruptedException ie) {
q.interruptControl = -1;
}
ForkJoinPool#managedBlock 会判断当前线程是否被阻塞,如果被阻塞并且线程类型是 ForkJoinWorkerThread 则会尝试创建补偿线程,否则等待阻塞结束。
5.【注解5】:到达这一步说明 this 已经直接结束,真正的收尾工作
if (q != null) {
// q.thread 置为空,表示q 死亡
q.thread = null;
// 如果 q 被中断了
if (q.interruptControl < 0) {
// 如果当前是可中断的返回 null
if (interruptible)
r = null; // report interruption
else
// 修改当前线程中断标志位,告知线程被终止,交由调用者来处理具体的情况
// 如果当前线程处于阻塞状态则退出阻塞状态并抛出 InterruptedException 异常
// 如果当前线程非阻塞状态,则修改当前线程中断标志位为 true,线程会继续执行
Thread.currentThread().interrupt();
}
}
以下,是个人的理解和推测,可能会出现错误,感谢指正。
CompletableFuture.Signaller 实现如下:
// 完成记录和释放等待线程。此类实现 ManagedBlocker 以避免在 ForkJoinPools 中堆积的阻塞操作时出现饥饿。
@SuppressWarnings("serial")
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;
// interruptible : 任务是否可终止
// nanos : 阻塞等待时间(0一直等待)
// deadline : 死亡时间,到达这个时间不再等待
Signaller(boolean interruptible, long nanos, long deadline) {
// 缓存当前线程,该线程是调用 join 方法的线程
this.thread = Thread.currentThread();
// 1 : 任务可终止,0 任务不可终止,-1 任务需要终止
this.interruptControl = interruptible ? 1 : 0;
this.nanos = nanos;
this.deadline = deadline;
}
// 触发操作,这里的操作即是通过 LockSupport.unpark(w); 唤醒 w 线程。
// 调用该方法的线程一定不是w 线程
final CompletableFuture<?> tryFire(int ignore) {
Thread w; // no need to atomically claim
if ((w = thread) != null) {
thread = null;
// 唤醒 w 线程
LockSupport.unpark(w);
}
return null;
}
// 判断当前线程是否需要阻塞, true 表示不需要阻塞
public boolean isReleasable() {
// 线程为空,表明当前 Signaller 已经死亡(执行结束),所以未阻塞
if (thread == null)
return true;
// 判断当前线程是否被标记了中断,如果中断并且允许中断则表明未阻塞
if (Thread.interrupted()) {
int i = interruptControl;
// 标志位 -1,表示任务已经被终止
interruptControl = -1;
// i > 0 表示当前任务允许被终止,返回 true
if (i > 0)
return true;
}
// 判断是否已经到达死亡时间(设置的最大等待时间), 达到时间说明
if (deadline != 0L &&
(nanos <= 0L || (nanos = deadline - System.nanoTime()) <= 0L)) {
// 线程置为空
thread = null;
return true;
}
// 返回 false
return false;
}
// 判读当前线程是否需要额外阻塞,true表示不需要,false 表示需要,
// 如果返回false在 ForkJoinPool#managedBlock 中会通过while 阻塞
public boolean block() {
// 当前线程不需要阻塞
if (isReleasable())
return true;
// deadline = 0 表明没有设置超时时间,通过 LockSupport.park(this);阻塞当前线程
else if (deadline == 0L)
// 阻塞当前线程
LockSupport.park(this);
else if (nanos > 0L)
// nanos 表示阻塞剩余时间,通过 LockSupport.parkNanos(this, nanos); 阻塞当前线程
LockSupport.parkNanos(this, nanos);
// 再次判断当前线程是否需要阻塞
return isReleasable();
}
// 判断当前操作是否还存货
final boolean isLive() { return thread != null; }
}
这里我们来看 Signaller 的三个方法
我们结合 ForkJoinPool#managedBlock来看下
public static void managedBlock(ManagedBlocker blocker)
throws InterruptedException {
ForkJoinPool p;
ForkJoinWorkerThread wt;
Thread t = Thread.currentThread();
// 判断当前线程类型,如果是 ForkJoinWorkerThread 线程 并且线程池不为空
if ((t instanceof ForkJoinWorkerThread) &&
(p = (wt = (ForkJoinWorkerThread)t).pool) != null) {
WorkQueue w = wt.workQueue;
// 判断当前线程是否阻塞,返回false则说明需要阻塞进入while 循环
while (!blocker.isReleasable()) {
// 进入这里说明线程阻塞了,尝试创建补偿线程
if (p.tryCompensate(w)) {
try {
// 当 线程处于需要阻塞 && 当前线程需要额外阻塞,通过while 循环阻塞线程
do {} while (!blocker.isReleasable() &&
!blocker.block());
} finally {
// //线程池的活动线程数+1
U.getAndAddLong(p, CTL, AC_UNIT);
}
break;
}
}
}
else {
// 线程类型不是 ForkJoinWorkerThread 则不能创建补偿线程只能阻塞。
do {} while (!blocker.isReleasable() &&
!blocker.block());
}
}
这里来总结下 CompletableFuture.Signaller 使用的目的:为了确保调用线程没有阻塞以及挂起线程等待任务结束,如果阻塞在允许的情况下创建补偿线程以保证效率。
ForkJoinPool#managedBlock 针对 ForkJoinWorkerThread 线程会创建补偿线程,关于 ForkJoinPool#managedBlock 补偿线程的具体描述详参 关于ForkJoinPool使用ManagedBlocker防线程阻塞而降低吞吐量的说明,本文篇幅所限,不再赘述。
以上:内容部分参考
https://blog.csdn.net/PNGYUL/article/details/119838961
https://blog.csdn.net/wenhonglian/article/details/119962525
https://blog.csdn.net/MSSPLANET/article/details/121487012
https://blog.csdn.net/java_xiaoo/article/details/120668986
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正