簡體   English   中英

為什么我的 CompletableFuture 代碼在 Java 8 中運行,但在 Java 11 中沒有運行?

[英]Why does my CompletableFuture code run in Java 8 but not in Java 11?

為什么此代碼塊在 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的文檔

返回一個新的 CompletableFuture,它在運行給定操作后由ForkJoinPool.commonPool()中運行的任務異步完成。

好的......讓我們看看ForkJoinPool.commonPool會告訴我們什么(強調我的):

返回公共池實例。 這個池是靜態構建的; 它的運行 state 不受shutdown()shutdownNow()嘗試的影響。 然而,這個池和任何正在進行的處理都會在程序System.exit(int)時自動終止 任何依賴異步任務處理在程序終止之前完成的程序都應該在退出之前調用commonPool().awaitQuiescence

啊哈,這就是為什么我們在使用公共池時看不到倒計時,這是因為公共池將在系統退出時終止,這正是我們從方法返回並退出程序時發生的情況(假設您的示例是就像你展示的那樣簡單......就像在main中調用一個方法......無論如何)

那么為什么自定義執行器會起作用呢? 因為,正如您已經注意到的那樣,該執行程序尚未終止。 后台仍然有一段代碼在運行,雖然很閑,但是Java沒有能力停下來。


那么我們現在能做什么呢?

一種選擇是做我們自己的 executor 並在我們完成后將其關閉,就像您建議的那樣。 我會爭辯說,這種方法畢竟不是那么糟糕。

第二種選擇是遵循 java 文檔所說的內容。

任何依賴異步任務處理在程序終止之前完成的程序都應該在退出之前調用commonPool().awaitQuiescence

public boolean awaitQuiescence(long timeout, TimeUnit unit)

如果由在此池中運行的 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()你需要的任何東西作為返回值。 我最喜歡這個,因為這樣的代碼最干凈易懂。 此外,我可以將我想要的所有 CF鏈接起來,並用它們做一些時髦的事情。


如果您不需要返回值並且不需要做任何其他事情並且只想要一個簡單的字符串返回和從 1 到 20 計數的異步處理,那么只需推動ForkJoinPool.commonPool().awaitQuiescence(1000, TimeUnit.SECONDS); 在您方便的地方並給它一些荒謬的超時,從而保證您將退出所有空閑進程。

如果它必須是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";
}

我會說你的程序只是按預期運行。 runAsync將在公共分叉連接池中運行提供的操作,除非您提供執行程序。 在任何情況下,如果您不等待可完成的未來完成,則test2方法會立即打印“完成”並返回。

“完成”可以隨時打印:您可能會看到“完成,1,......”或“1,完成,2......”等......這是一個競爭條件。

當你不使用執行器時,因為公共池中的線程是守護線程,你的程序可能隨時退出,並且不會等待調度的動作完成。

當您使用具有非守護線程的執行程序(通常是默認值)時,程序不會退出,直到執行程序關閉。

確保您的程序在您的操作完成之前不會退出的唯一方法是等待可完成的未來完成,按照另一個答案中的建議調用getjoin

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM