为什么这段代码在 Java 8 和 Java 11 中的行为不同?
private static String test2() {
CompletableFuture
.runAsync(() -> IntStream.rangeClosed(1, 20).forEach(x -> {
try {
Thread.sleep(500);
System.out.println(x);
} catch (InterruptedException e) {
e.printStackTrace();
}
}));
return "Finish";
}
我希望它打印完成,然后以 500 毫秒的间隔打印从 1 到 20 的数字,然后停止执行,它在 Java 8 中正常工作。但是,当我在 Java 11 上运行完全相同的方法时,它会打印 Finish 并在不调用 runAsync(...) 代码的情况下终止。我设法通过添加这样的 ExecutorService 来启动它
private static String test2() {
final ExecutorService executorService = Executors.newFixedThreadPool(10);
CompletableFuture
.runAsync(() -> IntStream.rangeClosed(1, 10).forEach(x -> {
try {
Thread.sleep(500);
System.out.println(x);
} catch (InterruptedException e) {
e.printStackTrace();
}
}), executorService);
return "Finish";
}
现在它被执行了,但没有完成;它到了 10 点,然后就坐着没有完成。我想出了如何通过调用 executorService.shutdown();
来停止执行。就在返回之前,但我 100% 确定这种方法是错误的,因为通常我会为许多方法使用相同的 executorService,如果我关闭它,其他方法也将无法执行。Java 8 和 Java 11 之间发生了什么变化,为什么我现在必须添加显式执行器服务,最重要的是如何正确完成方法执行?
最佳答案
TL;DR - 添加ForkJoinPool.commonPool().awaitQuiescence(1000, TimeUnit.SECONDS);
在您调用 CompletableFuture.runAsync
之后在您的代码末尾 这样System.exit
不会停止您的可运行文件。这样你就会得到你的行为。
更长的答案:
好的,首先,我在 Oracles java 8、OpenJDK 8 和 OpenJDK 11 中尝试了这两个示例。全面一致的行为,所以我的回答是,在这些不同 Java 版本的实现中没有任何改变会导致这种差异。在 两个例如,您看到的行为与 Java 告诉您的行为一致。
来自 CompletableFuture.runAsync
的文档
Returns a new CompletableFuture that is asynchronously completed by a task running in the
ForkJoinPool.commonPool()
after it runs the given action.
好吧...让我们看看
ForkJoinPool.commonPool
会告诉我们(强调我的):Returns the common pool instance. This pool is statically constructed; its run state is unaffected by attempts to
shutdown()
orshutdownNow()
. However this pool and any ongoing processing are automatically terminated upon programSystem.exit(int)
. Any program that relies on asynchronous task processing to complete before program termination should invokecommonPool().awaitQuiescence
, before exit.
啊哈,这就是为什么我们在使用公共(public)池时看不到倒计时,这是因为公共(public)池将在系统退出时终止,这正是我们从方法返回并退出程序时发生的情况(假设您的示例是真的就像你展示的那样简单......就像在
main
中调用单个方法一样......无论如何)那么为什么自定义执行器会起作用呢?因为,正如您已经注意到的那样,该执行程序尚未终止。后台仍然有一段代码在运行,虽然很闲,但是 Java 没有能力停下来。
那么我们现在能做什么呢?
一个选项是做我们自己的执行者,一旦我们完成就关闭它,就像你建议的那样。我认为这种方法 不是 毕竟不好用。
第二选项是遵循 java 文档所说的内容。
Any program that relies on asynchronous task processing to complete before program termination should invoke
commonPool().awaitQuiescence
, before exit.
public boolean awaitQuiescence(long timeout, TimeUnit unit)
If called by a ForkJoinTask operating in this pool, equivalent in effect to ForkJoinTask.helpQuiesce(). Otherwise, waits and/or attempts to assist performing tasks until this pool isQuiescent() or the indicated timeout elapses.
因此我们可以调用该方法并为公共(public)池中的所有公共(public)进程指定超时。我的观点是,这在某种程度上是特定于业务的,因为现在您必须回答这个问题 - 现在超时应该是什么鬼? .
第三选项是使用
CompletableFuture
的力量s 并吊起这个 runAsync
变量的方法:CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> ...
...
...
bla bla bla code bla bla bla
...
...
voidCompletableFuture.join();
// or if you want to handle exceptions, use get
voidCompletableFuture.get();
然后在你需要它的时候,你join()/get()
您需要作为返回值的任何内容。我最喜欢这个,因为这样的代码最干净易懂。此外,我可以将我想要的所有 CF 链接起来,并用它们做一些时髦的事情。在 的情况下你不需要返回值,也不需要做任何其他事情,只想要一个简单的字符串返回和从 1 到 20 计数的异步处理,然后只需推
ForkJoinPool.commonPool().awaitQuiescence(1000, TimeUnit.SECONDS);
在你方便的地方,给它一些可笑的超时,因此 保证您将退出所有空闲进程。
关于java - 为什么我的 CompletableFuture 代码在 Java 8 中运行而不在 Java 11 中运行?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67164198/