java - 为什么我的 CompletableFuture 代码在 Java 8 中运行而不在 Java 11 中运行?

标签 java multithreading asynchronous java-11 completable-future

为什么这段代码在 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() or shutdownNow(). However this pool and any ongoing processing are automatically terminated upon program System.exit(int). Any program that relies on asynchronous task processing to complete before program termination should invoke commonPool().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/

相关文章:

c# - 线程信号基础

jquery - 如何使用 jquery deferred.then() 函数?

javascript - 开始获取事件,但等待其本身和另一个 Promise 解决 - JavaScript

java - 提取二进制的最后 2 位

java - 字符串数组问题的数组列表

java - sleep方法会中断什么线程?

ruby - Ruby 中的任务/ future

java - 如果尝试实现接口(interface),Gradle 构建会失败

java - 运行 Servlet 时出现 RuntimeException : Tomcat restarting on its own

c++ - 在多线程应用程序中使用 OpenSSL