为什么即使我不调用 get() 或 join(),这个 CompletableFuture 也能工作?

2022-09-02 20:32:12

我在学习时遇到了一个问题。/ 方法正在阻止调用。如果我不给他们中的任何一个打电话怎么办?CompletableFutureget()join()

此代码调用:get()

// Case 1 - Use get()
CompletableFuture.runAsync(() -> {
    try {
        Thread.sleep(1_000L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("Hello");
}).get();
System.out.println("World!");

Thread.sleep(5_000L); // Don't finish the main thread

输出:

Hello
World!

此代码既不调用也不调用 :get()join()

// Case 2 - Don't use get()
CompletableFuture.runAsync(() -> {
    try {
        Thread.sleep(1_000L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("Hello");
});
System.out.println("World!");

Thread.sleep(5_000L); // For don't finish main thread

输出:

World!
Hello

我不知道为什么案例2的可运行块正在工作。


答案 1

整个想法是,它们被立即安排启动(尽管您无法可靠地判断它们将在哪个线程中执行),并且当您到达或时,结果可能已经准备就绪,即:可能已经完成。在内部,一旦管道中的某个阶段准备就绪,该特定阶段将被设置为完成。例如:CompletableFuturegetjoinCompletableFutureCompletableFuture

String result = 
   CompletableFuture.supplyAsync(() -> "ab")
                    .thenApply(String::toUpperCase)
                    .thenApply(x -> x.substring(1))
                    .join();

与以下内容相同:

CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> "ab");
CompletableFuture<String> cf2 = cf1.thenApply(String::toUpperCase);
CompletableFuture<String> cf3 = cf2.thenApply(x -> x.substring(1));
String result = cf3.join();

当你到达实际调用时,可能已经完成。 并且只是阻止直到所有阶段都完成,它不会触发计算;立即安排计算。joincf3getjoin


一个小的补充是,您可以在不等待管道执行完成的情况下完成:如 ,, ,(即使已经完成,这个也设置它),或 。这里有一个有趣的例子:CompletableFuturecompletecompleteExceptionallyobtrudeValueobtrudeExceptioncancel

 public static void main(String[] args) {
    CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
        System.out.println("started work");
        LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));
        System.out.println("done work");
        return "a";
    });

    LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
    cf.complete("b");
    System.out.println(cf.join());
}

这将输出:

started work
b

因此,即使工作开始了,最终值也是 ,而不是 。ba


答案 2

我不知道为什么case2的块正在工作。Runnable

没有理由为什么它不起作用。

该方法说要异步执行任务。假设应用程序不会过早结束,则无论您是否等待它完成,任务最终都将完成。runAsync(...)

提供了等待任务完成的各种方法。但在您的示例中,您没有将其用于该目的。相反,main 方法中的调用具有相同的效果;也就是说,它等待的时间足够长,以至于任务(可能)已经完成。输出之前也是如此。CompletableFutureThread.sleep(...)"Hello""World"

只是重申一下,调用不会导致任务发生。相反,它等待发生get()


使用等待事件(例如任务完成)发生是一个坏主意:sleep

  1. 睡眠并不能判断事件是否已经发生!
  2. 您通常不知道事件发生需要多长时间,您不知道睡多久。
  3. 如果你睡得太久,你就有“死时间”(见下文)。
  4. 如果你睡得不够久,事件可能还没有发生。所以你需要一次又一次地测试和睡觉,再一次,然后......

即使在此示例中,理论上 in main 也可能在 in 任务之前完成1sleepsleep

基本上,其目的是提供一种有效的方法来等待任务完成并提供结果。你应该使用它...CompletableFuture

为了说明。您的应用程序在输出和 之间等待(并浪费)约 4 秒。如果您按预期使用的方式使用,则不会有这4秒的“死区时间”。"Hello""World!"CompletableFuture


1 - 例如,某些外部代理可能能够有选择地“暂停”正在运行任务的线程。这可以通过设置断点来完成...