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

San*_*Lee 15 java multithreading completable-future

我在学习时有一个问题CompletableFuture。将get()/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
Run Code Online (Sandbox Code Playgroud)

输出:

Hello
World!
Run Code Online (Sandbox Code Playgroud)

此代码既不调用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
Run Code Online (Sandbox Code Playgroud)

输出:

World!
Hello
Run Code Online (Sandbox Code Playgroud)

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

Ste*_*n C 12

我不知道为什么Runnablecase2 块在工作。

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

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

CompletableFuture规定的等待任务完全不同的方式。但是在您的示例中,您并没有为此目的使用它。相反,Thread.sleep(...)您的 main 方法中的调用具有相同的效果;即它等待的时间足够长,任务已经(可能)完成。"Hello"之前的输出也是如此"World"

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


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

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

即使在这个例子中,在理论上是可行1sleep主前完成sleep的任务。

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

为了显示。您的应用程序在输出"Hello""World!". 如果您CompletableFuture按预期使用 ,则不会有那 4 秒的“死区时间”。


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


Eug*_*ene 9

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

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

是一样的:

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();
Run Code Online (Sandbox Code Playgroud)

到您实际调用 时joincf3可能已经完成。get并且join只是阻塞直到所有阶段都完成,它不会触发计算;立即安排计算。


一个小补充是,您可以在CompletableFuture不等待管道执行完成的情况下完成 a :像complete, completeExceptionally, obtrudeValue(即使它已经完成,这个设置它),obtrudeExceptioncancel。这是一个有趣的例子:

 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());
}
Run Code Online (Sandbox Code Playgroud)

这将输出:

started work
b
Run Code Online (Sandbox Code Playgroud)

所以即使工作开始了,最终的值是b,而不是a


ara*_*ran 6

第二种情况是“工作”,因为你让主线程睡了足够长的时间(5 秒)。工作在引号之间,因为它不是真的工作,只是完成。我在这里假设代码应该输出Hello World!,以便被认为“正常工作”。


在这两种情况下,在主线程结束时尝试使用此睡眠时间的相同代码:

Thread.sleep(100);

1 . 第一个会以相同的方式运行,因为 get 操作会阻塞主线程。事实上,对于第一种情况,你甚至不需要最后的睡眠时间。

输出: Hello World!


2 . 第二种情况不会输出Hello,因为没有人告诉主线程:“嘿,等待这个完成”。这就是get():阻止调用者以等待任务完成。没有它,并在最后设置一个低睡眠时间,可运行被调用,但在主线程停止之前无法完成其工作。

输出: World!


这也是为什么在第一种情况下Hello World!首先是 runnable 的输出,然后是 main 的输出 - 意味着主线程被阻塞直到get()返回)被写入,而第二个显示出阅读障碍的微妙迹象的原因:World Hello!

但它不是阅读障碍,它只是执行它被告知的。在第二种情况下,会发生这种情况:

1. runnable 被调用

2.主线程继续其进程,打印 ("World!)

3. Sleep时间设置:可运行1秒/主5秒。(runnable 的 sleep 也可以在第二步执行,但我把它放在这里是为了澄清行为

4. runnable 任务在 1 秒后打印 ("Hello"),CompletableFuture 完成。

5. 5 秒过去了,主线程停止。

所以你的 runnable 可以打印,Hello因为它能够在这 5 秒超时之间执行命令。

World! . . . . . .(1)Hello. . . . . . . . . . .(5)[END]
Run Code Online (Sandbox Code Playgroud)

例如,如果将最后 5 秒的超时时间减少到 0.5 秒,则会得到

World!. . (0.5)[END]
Run Code Online (Sandbox Code Playgroud)

  • 看来你在这里混淆了很多概念,而事实是这很简单。我建议您阅读什么是守护线程(它不是_低优先级线程_)。线程的优先级是完全不同的事情。 (2认同)