为什么即使我不调用 get() 或 join(),这个 CompletableFuture 也能工作?
Why does this CompletableFuture work even when I don't call get() or join()?
我在学习时遇到了一个问题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
输出:
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
不知道case 2的runnable block为什么能运行
第二种情况是“working”,因为你让主线程休眠了足够长的时间(5 秒)。工作在引号之间,因为它 并没有真正工作 ,只是完成。我在这里假设代码应该输出 Hello World!
以便被认为是“正常工作”。
在这两种情况下,在主线程末尾使用此睡眠时间尝试相同的代码:
Thread.sleep(100);
1。第一个将以相同的方式运行,因为 get 操作会阻塞主线程。其实对于第一种情况,你连最后的睡眠时间都不需要。
输出:Hello World!
2。第二种情况不会输出 Hello
,因为没有人告诉主线程:“嘿,等待它完成 ”。这就是 get()
所做的:阻止调用者以等待任务完成。没有它,并在最后设置一个较短的睡眠时间,runnable 被调用,但无法在主线程停止之前完成它的工作。
输出:World!
这也是第一种情况 Hello World!
的原因(首先是 runnable 的输出,然后是 main 的输出 - 这意味着主线程被阻塞,直到 get()
返回) 是这样写的,而第二个则显示出阅读障碍的微妙迹象:World Hello!
但这不是阅读障碍,它只是执行它被告知的事情。在第二种情况下,会发生这种情况:
1. runnable 调用.
2. 主线程继续其进程,printing ("World!)
3. Sleep
时间设置为:runnable 1 秒/main 5 秒。 (runnable的sleep也可以在第二步执行,但我把它放在这里是为了阐明行为)
4. 可运行任务 1 秒后打印 ("Hello"),CompletableFuture 完成。
5. 5 秒过去了,主线程停止了。
所以你的 runnable 可以打印 Hello
因为它能够在这 5 秒超时之间执行命令。
World! . . . . . .(1)Hello. . . . . . . . . . .(5)[END]
如果您减少最后 5 秒的超时,例如,减少到 0.5 秒,您会得到
World!. . (0.5)[END]
I don't know why the Runnable
block of case2 is working.
没有理由不工作。
runAsync(...)
方法表示异步执行任务。假设应用程序没有提前结束,任务最终会完成,无论您是否等待它完成。
CompletableFuture
提供了多种等待任务完成的方式。但是在您的示例中,您并没有将它用于该目的。相反,您的 main 方法中的 Thread.sleep(...)
调用 具有相同的效果 ;也就是说,它等待的时间足够长,任务已经(可能)完成了。所以 "Hello"
在 "World"
.
之前输出
重申一下,get()
调用不会导致 任务发生。相反,它 等待 直到 发生 。
使用 sleep
等待事件(例如任务完成)发生是个坏主意:
- 睡眠并不能判断事件是否已经发生!
- 你通常不知道事件发生需要多长时间,你不知道要睡多久。
- 如果你睡得太久,你就会有“死时间”(见下文)。
- 如果你睡的时间不够长,事件可能还没有发生。所以你需要一次又一次地测试和睡觉,然后......
即使在这个例子中,理论上 1 main 中的 sleep
可以在任务中的 sleep
之前完成。
基本上,CompletableFuture
的目的是提供一种有效的方法来等待任务完成并交付结果。你应该使用它...
举例说明。您的应用程序在输出 "Hello"
和 "World!"
之间等待(并浪费)~4 秒。如果您按预期使用 CompletableFuture
,则不会有那 4 秒的“死时间”。
1 - 例如,某些外部代理可能能够有选择地“暂停”作为 运行 任务的线程。可以通过设置断点来完成...
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
(这一个设置它,即使它已经完成),obtrudeException
或 cancel
。这是一个有趣的例子:
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
。
我在学习时遇到了一个问题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
输出:
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
不知道case 2的runnable block为什么能运行
第二种情况是“working”,因为你让主线程休眠了足够长的时间(5 秒)。工作在引号之间,因为它 并没有真正工作 ,只是完成。我在这里假设代码应该输出 Hello World!
以便被认为是“正常工作”。
在这两种情况下,在主线程末尾使用此睡眠时间尝试相同的代码:
Thread.sleep(100);
1。第一个将以相同的方式运行,因为 get 操作会阻塞主线程。其实对于第一种情况,你连最后的睡眠时间都不需要。
输出:Hello World!
2。第二种情况不会输出 Hello
,因为没有人告诉主线程:“嘿,等待它完成 ”。这就是 get()
所做的:阻止调用者以等待任务完成。没有它,并在最后设置一个较短的睡眠时间,runnable 被调用,但无法在主线程停止之前完成它的工作。
输出:World!
这也是第一种情况 Hello World!
的原因(首先是 runnable 的输出,然后是 main 的输出 - 这意味着主线程被阻塞,直到 get()
返回) 是这样写的,而第二个则显示出阅读障碍的微妙迹象:World Hello!
但这不是阅读障碍,它只是执行它被告知的事情。在第二种情况下,会发生这种情况:
1. runnable 调用.
2. 主线程继续其进程,printing ("World!)
3. Sleep
时间设置为:runnable 1 秒/main 5 秒。 (runnable的sleep也可以在第二步执行,但我把它放在这里是为了阐明行为)
4. 可运行任务 1 秒后打印 ("Hello"),CompletableFuture 完成。
5. 5 秒过去了,主线程停止了。
所以你的 runnable 可以打印 Hello
因为它能够在这 5 秒超时之间执行命令。
World! . . . . . .(1)Hello. . . . . . . . . . .(5)[END]
如果您减少最后 5 秒的超时,例如,减少到 0.5 秒,您会得到
World!. . (0.5)[END]
I don't know why the
Runnable
block of case2 is working.
没有理由不工作。
runAsync(...)
方法表示异步执行任务。假设应用程序没有提前结束,任务最终会完成,无论您是否等待它完成。
CompletableFuture
提供了多种等待任务完成的方式。但是在您的示例中,您并没有将它用于该目的。相反,您的 main 方法中的 Thread.sleep(...)
调用 具有相同的效果 ;也就是说,它等待的时间足够长,任务已经(可能)完成了。所以 "Hello"
在 "World"
.
重申一下,get()
调用不会导致 任务发生。相反,它 等待 直到 发生 。
使用 sleep
等待事件(例如任务完成)发生是个坏主意:
- 睡眠并不能判断事件是否已经发生!
- 你通常不知道事件发生需要多长时间,你不知道要睡多久。
- 如果你睡得太久,你就会有“死时间”(见下文)。
- 如果你睡的时间不够长,事件可能还没有发生。所以你需要一次又一次地测试和睡觉,然后......
即使在这个例子中,理论上 1 main 中的 sleep
可以在任务中的 sleep
之前完成。
基本上,CompletableFuture
的目的是提供一种有效的方法来等待任务完成并交付结果。你应该使用它...
举例说明。您的应用程序在输出 "Hello"
和 "World!"
之间等待(并浪费)~4 秒。如果您按预期使用 CompletableFuture
,则不会有那 4 秒的“死时间”。
1 - 例如,某些外部代理可能能够有选择地“暂停”作为 运行 任务的线程。可以通过设置断点来完成...
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
(这一个设置它,即使它已经完成),obtrudeException
或 cancel
。这是一个有趣的例子:
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
。