首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么我的CompletableFuture代码在Java 8中运行,而在Java 11中不运行?

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

Stack Overflow用户
提问于 2021-04-19 14:46:37
回答 3查看 1.6K关注 0票数 8

为什么这段代码在Java 8和Java 11中的行为不同?

代码语言:javascript
复制
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来开始它

代码语言:javascript
复制
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服务,最重要的是如何正确地完成方法的执行?

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 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方法提升到一个变量:

代码语言:javascript
复制
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);推到对您方便的地方,给它一些可笑的超时,这样就可以在所有空闲进程上退出。

票数 6
EN

Stack Overflow用户

发布于 2021-04-19 15:49:15

如果必须是CompletableFuture

代码语言:javascript
复制
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的替代品

代码语言:javascript
复制
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";
}
票数 1
EN

Stack Overflow用户

发布于 2021-04-20 10:33:29

我会说你的程序就像预期的那样。除非您提供了执行器,否则runAsync将在公共叉连接池中运行提供的操作。无论如何,如果不等待可完成的未来完成,test2方法将立即打印"Finish“并返回。

“完成”可以随时打印:你可以看到“完成,1,.”或者"1,完成,2.“等等。这是比赛条件。

当您不使用executor时,因为公共池中的线程是守护进程线程,您的程序可能随时退出,并且不会等待预定的操作完成。

当您使用带有非守护进程线程的执行器(这通常是默认的)时,程序将在执行器关闭之前不会退出。

确保您的程序在操作完成之前不退出的唯一方法是等待可完成的未来完成,按照另一个答案中的建议调用到达加入

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67164198

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档