为什么这段代码在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";
}我预期它会打印Finish,然后用500 ms间隔打印数字从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之间发生了什么变化,为什么我现在必须添加一个显式的executor服务,最重要的是如何正确地完成方法的执行?
发布于 2021-04-20 16:57:38
TL;DR --在调用CompletableFuture.runAsync 之后并在代码的末尾添加ForkJoinPool.commonPool().awaitQuiescence(1000, TimeUnit.SECONDS);,这样System.exit就不会停止运行。这样你就会有行为举止了。
更长的答案:
好的,首先,我尝试了Oracles 8、OpenJDK 8和OpenJDK 11这两个例子。一致的行为,所以我的答案是,在不同java版本的实现中,没有什么改变会导致这种差异。在的两个示例中,您看到的行为与告诉您的行为是一致的。
来自CompletableFuture.runAsync的文档
返回一个新的CompletableFuture,该任务在运行给定操作后由运行在
ForkJoinPool.commonPool()中的任务异步完成。
好吧..。让我们看看ForkJoinPool.commonPool将告诉我们什么(强调我的):
返回公共池实例。这个池是静态构造的;它的运行状态不受
shutdown()或shutdownNow()尝试的影响。但是,此池和任何正在进行的处理将在程序System.exit(int).中自动终止。在程序终止之前,任何commonPool().awaitQuiescence,依赖异步任务处理来完成的程序,都应该在退出之前调用。
Aha,这就是为什么我们在使用公共池时没有看到倒计时,因为公共池将在系统退出时终止,这正是当我们从方法返回并退出程序时所发生的事情(假设您的示例确实像您展示的那样简单.)。就像main中的一个方法调用.无论如何)
那么,为什么定制的执行者会工作呢?因为,正如你已经注意到的,那个执行者还没有被终止。在后台仍然有一段代码在运行,尽管是无聊的,但Java没有停止的能力。
那我们现在能做什么呢?
One选项是完成我们自己的执行器并关闭它,就像您建议的那样。我认为这种方法----毕竟并不是那么糟糕。
第二个选项是遵循java所说的。
任何在程序终止前依赖异步任务处理来完成的程序都应该在退出之前调用
commonPool().awaitQuiescence。公共布尔型(公共布尔型,非服务型)(长超时,时间单位单位) 如果由在此池中操作的ForkJoinTask调用,则等效于ForkJoinTask.helpQuiesce()。否则,等待和/或尝试协助执行任务,直到这个池isQuiescent()或指定的超时结束。
因此,我们可以调用该方法,并为公共池中的所有公共进程指定一个超时。我的观点是,这在某种程度上是特定于业务的,因为现在您必须回答这个问题--,现在的超时应该是什么??。
第三个选项是使用CompletableFuture的威力,并将这个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()。我最喜欢这样做,因为代码是最干净的,也是可以理解的。此外,我可以把我的CFs的所有我想做的,并与他们做时髦的东西。
在中,您不需要返回值,也不需要执行任何其他操作,只需要简单地返回字符串和从1到20计数的异步处理,然后把ForkJoinPool.commonPool().awaitQuiescence(1000, TimeUnit.SECONDS);推到对您方便的地方,给它一些可笑的超时,这样就可以在所有空闲进程上退出。
发布于 2021-04-19 15:49:15
如果必须是CompletableFuture
private static String test2() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
CompletableFuture.runAsync( () -> IntStream.rangeClosed(1, 20).forEach(x -> {
try {
Thread.sleep(500);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.err.println(x);
})).get();
}
catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}});
return "Finish";
}我认为你最好选择CompletableFuture的替代品
private static String test2() {
Runnable runner = new Runnable() {
@Override
public void run() {
IntStream.rangeClosed(1, 20).forEach(x -> {
try {
Thread.sleep(500);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(x);
});
}
};
Executors.newCachedThreadPool().execute(runner);
return "Finish";
}发布于 2021-04-20 10:33:29
https://stackoverflow.com/questions/67164198
复制相似问题