CompletableFuture 什么时候真正完成?

When is CompletableFuture actually completed?

这是 MCVE:

public static void main(String[] args) {
    CompletableFuture<String> r1 = CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return "41";
    });
    CompletableFuture<String> r2 = CompletableFuture.supplyAsync(() -> "42");
    CompletableFuture<String> r3 = CompletableFuture.supplyAsync(() -> {
        System.out.println("I'm called.");
        return "43";
    });
    CompletableFuture.allOf(r1, r2, r3).thenRun(() -> { System.out.println("End."); });
    Stream.of(r1, r2, r3).forEach(System.out::println);
}

有点奇怪,没有真正完成 allOf(...)CompletableFuture,例如调用它的 join(),我得到以下输出:

I'm called.
java.util.concurrent.CompletableFuture@<...>[Not completed, 1 dependents]
java.util.concurrent.CompletableFuture@<...>[Completed normally]
java.util.concurrent.CompletableFuture@<...>[Completed normally]

我可以知道是什么导致 JVM treat/think r11 (estimated number of) dependent CompletableFuture,而它决定直接完成 r2r3?我能看到的唯一区别就是 try-catch,那么答案就这么简单吗?

为了比较,当我最后实际执行 join() 时,我得到了 5 秒的预期等待时间和以下输出。如果有帮助,我在 Java 8 Update 40 JVM 上遇到了这个问题。

修改:

// ...
CompletableFuture.allOf(r1, r2, r3).thenRun(() -> { System.out.println("End."); }).join();
Stream.of(r1, r2, r3).forEach(System.out::println);

输出:

I'm called.
// <note: 5-second wait is here>
End.
java.util.concurrent.CompletableFuture@<...>[Completed normally]
java.util.concurrent.CompletableFuture@<...>[Completed normally]
java.util.concurrent.CompletableFuture@<...>[Completed normally]

r1r2 是两个独立提交的异步任务的 CompletableFuture

May I know what's causing the JVM to treat/think that r1 has 1 (estimated number of) dependent CompletableFuture, while it decides to straightforwardly complete r2 and r3

没有。当您在这些实例上调用 println 时,r2r3 已经正常完成(它们做的不多)。 r1 没有(本应完成它的线程很可能正在休眠)。

allOf 的调用没有阻塞。它将 return 自己的 CompletableFuture 完成,当你给他们的所有 CompletableFuture 完成后。你将它链接到另一个 CompletableFuturethenRun,因为 r2r3 已经完成,所以只依赖于 r1,即。 r1 完成时完成。

您选择放弃对此 CompletableFuture 的引用,但已安排提交给 thenRun 的任务。如果您添加

Thread.sleep(6000);

在原始程序的末尾,您会看到 End. 日志在 r1 完成时打印出来,因此 return 由 thenRun 编辑.


请注意,除非另有说明,您在 CompletableFuture 中的异步任务(例如通过 supplyAsync 提交)都是 运行 在默认 ForkJoinPool 中使用守护线程。您的应用程序将在 5 秒过去之前退出,除非您选择阻塞某处并等待该时间过去。