切換語言為:簡體

Java中CompletableFuture allOf呼叫的時候如果出現異常會怎麼樣

  • 爱糖宝
  • 2024-10-30
  • 2034
  • 0
  • 0

在日常開發中如果使用 CompletableFuture 的時候,如果出現異常,會怎麼樣呢,會只停止這個執行緒還是全部停止?會主動丟擲異常嗎,今天我透過一下案例來以結果來分析。

開發環境

# java 環境
openjdk 23.0.1 2024-10-15
OpenJDK Runtime Environment (build 23.0.1+11-39)
OpenJDK 64-Bit Server VM (build 23.0.1+11-39, mixed mode, sharing)

兩個 CompletableFuture 一個拋異常 一個不拋異常,執行結果是什麼呢

/**
 * 不拋異常 
 */
public CompletableFuture<Void> normalCompletableFuture() {
    return CompletableFuture.runAsync(() -> {
        // 這裏讓執行緒休眠10秒
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            log.info("睡眠過程中出現異常 -> {}", e);
        } finally {
            log.info("普通執行緒執行完畢");
        }
    });
}

/**
 * 拋異常的執行緒
 */
public CompletableFuture<Void> exceptionCompletableFuture() {
    return CompletableFuture.runAsync(() -> {
        throw new RuntimeException("runAsync -> 發生了一個大的異常");
    });
}

然後可以使用 allOf 來組合兩個執行緒

public void testRunRunAsync() {
    CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(this.exceptionCompletableFuture(), this.normalCompletableFuture());
    voidCompletableFuture.join();
}

執行以後得到以下的執行結果

09:22:56.319 [ForkJoinPool.commonPool-worker-1] INFO org.flow.basic.threadpool.CompletableFutureRuntimeDemo -- 普通執行緒執行完畢
java.lang.RuntimeException: runAsync -> 發生了一個大的異常
    at org.flow.basic.threadpool.CompletableFutureRuntimeDemo.lambda$exceptionCompletableFuture$1(CompletableFutureRuntimeDemo.java:38)
    at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1848)
    at java.base/java.util.concurrent.CompletableFuture$AsyncRun.exec(CompletableFuture.java:1840)
    at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:507)
    at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1458)
    at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:2034)
    at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:189)

我們可以看到雖然丟擲異常了,但是並不會因為異常而讓全部的執行的任務都停止,他會等待所有的任務都執行完畢,然後丟擲異常。那如果不實用 join 使用其他的 api 是否會丟擲異常呢? 把執行的程式碼略作改動我們再來執行看看效果

public void testRunRunAsync() {
    CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(this.exceptionCompletableFuture(), this.normalCompletableFuture());

    while (true) {
        if (voidCompletableFuture.isDone()) {
            log.info("執行緒執行完畢");
            break;
        }
    }
}

執行結果

09:36:27.601 [ForkJoinPool.commonPool-worker-1] INFO org.flow.basic.threadpool.CompletableFutureRuntimeDemo -- 普通執行緒執行完畢
09:36:27.604 [main] INFO org.flow.basic.threadpool.CompletableFutureRuntimeDemo -- 執行緒執行完畢

這個可以看出我們並沒有使用阻塞的方式,而是使用了 while 的死迴圈,這個會對 cpu 帶來壓力,不過我麼可以看到他並不會報錯並且兩個任務全部執行完畢。

兩個 CompletableFuture 一個拋異常 一個不拋異常,如果執行呢?

public void testRunRunAsync() {
    CompletableFuture<Void> voidCompletableFuture = CompletableFuture.allOf(this.exceptionCompletableFuture(), this.normalCompletableFuture());
    voidCompletableFuture.exceptionally(e -> {
        log.error("發生異常,主動處理 -> {}", e.getMessage());
        return null;
    }).join();
    log.info("正常執行完畢");
}

列印結果

09:51:36.359 [ForkJoinPool.commonPool-worker-1] INFO org.flow.basic.threadpool.CompletableFutureRuntimeDemo -- 普通執行緒執行完畢
09:51:36.362 [ForkJoinPool.commonPool-worker-1] ERROR org.flow.basic.threadpool.CompletableFutureRuntimeDemo -- 發生異常,主動處理 -> java.lang.RuntimeException: runAsync -> 發生了一個大的異常
09:51:36.363 [main] INFO org.flow.basic.threadpool.CompletableFutureRuntimeDemo -- 正常執行完畢

這個我們可以看出如果主動處理了異常,但是沒有使用 join 或者 isDone 等方式來判斷,那麼就會導致程式阻塞,並且不會丟擲異常。

結論

CompletableFuture 的 allOf 的時候如果出現異常,不會主動丟擲異常,但是會等待所有的任務都執行完畢,然後丟擲異常。如果不用 join 等待,也可以透過 isDone 來判斷是否執行完畢,這個並不會主動丟擲異常,這個要自己來判斷異常。但是會對 CPU 帶來負載,這個時候可以根據實際情況來做衡量取捨。但是如果使用了 exceptionally 來處理異常,那麼就會被異常處理攔截會根據自己的處理來做實際的異常行為

0則評論

您的電子郵件等資訊不會被公開,以下所有項目均必填

OK! You can skip this field.