JDK8 中的 CompletableFutures 什么时候阻塞执行线程?

When do CompletableFutures in JDK8 block the execution threads?

示例 1:

CometableFuture
    .supplyAsync(new MySupplier())
    .someCompletableFutureMethod(new SomeCompletableFutureComsumer())

ForkJoin 线程是否曾被阻塞?

示例 2:

final CompletableFuture cf = new CompletableFuture();
cf = executor.execute(new Runnable(){
    public void run(){
        //do work
        cf.complete(result);
    }
});
cf.whenComplete(new MyConsumer());

涉及的任何胎面是否被阻塞?

(我知道,我应该使用 Callable 而不是 Runnable :)

有什么方法可以在不使用从 Future 继承的方法的情况下滥用 API 来阻塞任何线程(main、ForkJoin、executor)?

假设我没有使用任何阻塞 APIs(我知道 future.get() 块)。

参见 javadoc get():

Waits if necessary for this future to complete, and then returns its result.

换句话说:当您在 CompletableFuture 上调用 "blocking" 方法时,它应该阻塞。否则不会。

该 javadoc 中的任何方法都没有这样的描述:可能会随机阻塞 ;-) !

CompletableFuture 添加了 join() 方法,这是 一种 Future.get() 的非检查异常版本(docs here). 不过我不建议使用它,因为如果不超时,它可能会挂起线程,而且您几乎总是可以使用 thenApply 和朋友重写代码。 我尝试通过始终使用 CompletionStage 来强制执行此操作,CompletableFuture 实现了这一点。

除了从 Future 继承的方法之外,我认为没有其他方法阻止。

所有这些方法都需要某种形式的同步,如果不阻塞就无法正确实现。

例如:

  1. supplyAsync() 尝试通过其 execute() 方法在公共 ForkJoin 池中排队作业。此方法依赖于 UnsafeawaitRunStateLock() 来排队作业;
  2. 类似的事情适用于您的 executor.execute(),尽管您的实施可能不同;
  3. 关于 someCompletableFutureMethod()(和 whenComplete()):
    1. 除了*Async()方法,它首先需要检查这个future是否已经完成:如果是这样,传入的函数将在调用线程上执行,你可以考虑作为阻塞(尽管它实际上是在执行您的代码);
    2. 否则,任务必须排队,这依赖于循环和 Unsafe 来完成——参见 CompletableFuture.*push*(*) 方法。
  4. cf.complete() 需要处理子任务,例如发送给 whenComplete() 的子任务:
    1. 它需要一些锁来确保这些任务只执行一次;
    2. 它将立即执行所有非异步任务(如 3.1 中);
    3. 它将需要安排异步任务(如 1. 和 2.)
  5. 大多数接受 lambda 作为参数的 CompletableFuture 方法(以及 return 一个新的 CompletableFuture)实际上隐藏了一个 complete() 调用(在 return ed future) 将由执行 lambda 的同一个线程执行,因此像 4.¹
  6. 中那样阻塞它

当然,除了在高度并发的环境中,许多线程试图同时推送和执行任务,你可能不会注意到这种阻塞。情况 3.1 和 4.2 是您最有可能遇到的情况,因为它们经常发生(如果您使用非 *Async() 方法)。

¹ thenCompose() 方法有一个微妙的例外,因为执行 complete() 调用的线程将取决于 CompletionStage return 是否由 lambda 编辑是否已经完成。此外,非静态 *Async() 方法似乎重用了此调用的执行程序,因此可以使用另一个线程。