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

yzckvree  于 2021-06-26  发布在  Java
关注(0)|答案(3)|浏览(694)

我在研究未来的时候有个问题。这个 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

输出:

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的可运行模块起作用了。

6qftjkof

6qftjkof1#

关于 CompletableFuture 它们是立即被安排启动的(尽管您不能可靠地判断它们将在哪个线程中执行),并且在您到达的时候 get 或者 join ,结果可能已经就绪,即 CompletableFuture 可能已经完成。在内部,一旦管道中的某个阶段准备就绪 CompletableFuture 将设置为“已完成”。例如:

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();

但当你真正开始 join , cf3 可能已经结束了。 get 以及 join 只需在所有阶段完成之前阻塞,它不会触发计算;立即安排计算。
一个小的补充是你可以完成一个 CompletableFuture 不必等待管道的执行完成:就像 complete , completeExceptionally , obtrudeValue (即使它已经完成了,这个也会设置它),等等。下面是一个有趣的例子:

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

因此,即使工作开始,最终的价值是 b ,不是 a .

2q5ifsrm

2q5ifsrm2#

第二种情况是“工作”的,因为您使主线程睡眠的时间足够长(5秒)。工作是在引号之间,因为它不是真正的工作,只是完成。我在这里假设代码应该输出 Hello World! 才能被视为“正常工作”。
在这两种情况下,请在主线程结束时尝试使用相同的代码: Thread.sleep(100); 1.第一个线程的行为方式相同,因为get操作阻塞了主线程。事实上,对于第一种情况,你甚至不需要最后的睡眠时间。
输出: Hello World! 2.第二种情况不会输出 Hello ,因为没有人告诉主线:“嘿,等这个结束”。那是什么 get() does:阻止调用者以等待任务完成。如果没有它,并且在最后设置一个较低的睡眠时间,就会调用runnable,但是无法在主线程停止之前完成它的工作。
输出: World! 这也是为什么在第一种情况下 Hello World! (首先是runnable的输出,然后是main的输出-这意味着主线程被阻塞,直到 get() 返回),而第二个显示了阅读障碍的微妙迹象: World Hello! 但它不是诵读困难,它只是执行它被告知的。在第二种情况下,会发生这种情况:
1.runnable被称为。
2.主线程继续它的进程,打印(“world!)
3. Sleep 设置时间:runnable上1秒/main上5秒(runnable的睡眠也可以在第二步执行,但我把它放在这里是为了澄清行为)
4.runnable任务在1秒后打印(“hello”),completablefuture完成。
5.5秒后,主线程停止。
所以你的runnable可以打印出来 Hello 因为它能够在这5秒超时之间执行命令。

World! . . . . . .(1)Hello. . . . . . . . . . .(5)[END]

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

World!. . (0.5)[END]
qv7cva1a

qv7cva1a3#

我不知道为什么 Runnable 第二组正在工作。
没有理由不起作用。
这个 runAsync(...) 方法表示异步执行任务。假设应用程序没有过早结束,不管您是否等待任务完成,任务最终都会完成。
这个 CompletableFuture 提供各种等待任务完成的方法。但在你的例子中,你并不是为了这个目的而使用它。取而代之的是 Thread.sleep(...) 调用你的主方法也有同样的效果;i、 等待的时间足够长,任务(可能)已经完成了。所以呢 "Hello" 之前是否有输出 "World" .
重申一下 get() 调用不会导致任务发生。而是等待它发生。
使用 sleep 等待事件(如任务完成)发生是个坏主意:
睡眠不能判断事件是否发生!
你通常不知道这件事要花多长时间才能发生,你也不知道要睡多久。
如果你睡得太久,你就有“死时间”(见下文)。
如果你睡眠时间不够长,事件可能还没有发生。所以你需要一次又一次的测试和睡眠。。。
即使在这个例子中,理论上也有可能 sleep 主要是在 sleep 在任务中。
基本上 CompletableFuture 是提供一种等待任务完成并交付结果的有效方法。你应该用它。。。
举例说明。您的应用程序在输出之间等待(并浪费)约4秒 "Hello" 以及 "World!" . 如果你用了 CompletableFuture 因为它的目的是使用,你不会有这4秒的“死区时间”。
1-例如,某些外部代理可能能够有选择地“暂停”正在运行任务的线程。可以通过设置断点来完成。。。

相关问题