非同步程式設計 CompletableFuture

KerryWu發表於2022-03-21

1. 前言

剛工作時,對於非同步程式設計的觀念,可能停留線上程、執行緒池的概念。建立一個新執行緒去執行,只執行用 Runnable,想要有返回值就用 Callable 返回 Future。再有經驗一點的,用執行緒池來管理執行緒。但這些都停留在基礎階段,到實際開發時會遇到各種複雜的應用場景,雖然併發包裡面也提供了一些像 SemaphoreCountDownLatchCyclicBarrier 的協作類,但還是不夠,這裡給大家介紹 jdk8 推出的 CompletableFuture

先列出來幾個實際開發中遇到的問題吧:

  • 程式碼中有一個非同步呼叫方法A,還有另外一個方法B需要依賴方法A呼叫完成後,處理方法A返回的結果。如果通過 future.get()等待方法A執行完成,無疑又回到了同步阻塞。
  • 程式碼中有多個非同步呼叫方法A、B、C、D,但最終的結果需要等所有非同步方法都執行完才能返回。這種場景也比較常見,微服務專案中,某個介面要同時獲取不同業務服務的資料。

這些問題,在學會了 CompletableFuture之後都不是問題,它的針對傳統非同步程式設計方式的提升,主要體現在支援了非同步回撥CompletableFuture 之於傳統非同步程式設計的提升,我覺得有點像 AIO(非同步非阻塞)之於 NIO(同步非阻塞)

下面是關於 CompletableFuture的用法介紹,它的方法很多很雜,不少功能性都有重疊。我是每個方法都試過之後,按照自己的理解做了分類,每個分類按照下面的單個章節來說明。

2. 基礎

CompletableFuture 類實現了 Future 和 CompletionStage 介面,因此依然可以使用原有 Future 提供的方法,但新的特性都在 CompletionStage 裡面。

建立 CompletableFuture

CompletableFuture 提供了幾個 static 方法,它們使用任務來例項化一個 CompletableFuture 例項。

CompletableFuture<Void> CompletableFuture.runAsync(Runnable runnable);
CompletableFuture<Void> CompletableFuture.runAsync(Runnable runnable, Executor executor);

CompletableFuture<U> CompletableFuture.supplyAsync(Supplier<U> supplier);
CompletableFuture<U> CompletableFuture.supplyAsync(Supplier<U> supplier, Executor executor)

這裡對比下幾個區別:

  • runAsync 類似 Runnable,注重執行任務,無返回值,因此返回 CompletableFuture<Void>。而 supplyAsync 類似 Callable,有返回值 CompletableFuture<U>。
  • 這兩個方法的帶 executor 的過載方法,表示讓任務在指定的執行緒池中執行,不指定的話,通常任務是在 ForkJoinPool.commonPool() 執行緒池中執行的。後續介紹的其他 CompletableFuture 方法基本都有帶 executor 的過載方法。
Future 方法

因為 實現了 Future 介面,所以依然可以使用原有 Future 提供的方法。

public T     get()
public T     get(long timeout, TimeUnit unit)
public T     getNow(T valueIfAbsent)
public T     join()
  • getNow有點特殊,如果結果已經計算完則返回結果或者丟擲異常,否則返回給定的valueIfAbsent值。
  • join返回計算的結果或者丟擲一個unchecked異常(CompletionException),它和get對丟擲的異常的處理有些細微的區別。
顯示完成
  • boolean complete(T value):如果尚未完成,則將get()和相關方法返回的值設定為給定值。如果完成了,則正常返回 get()值。
  • boolean completeExceptionally(Throwable ex):如果尚未完成,則導致呼叫get()和相關方法丟擲給定的異常。如果完成了,則正常返回 get()值。

有個例子:

    public static CompletableFuture<Integer> divideNumber(int a, int b) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return a / b;
        });
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        log.debug("begin");
        CompletableFuture<Integer> intFuture = divideNumber(10, 2);
        new Thread(() -> {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            intFuture.complete(0);
        }).start();

        CompletableFuture<Integer> exceptionFuture = divideNumber(10, 2);
        new Thread(()->{
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            exceptionFuture.completeExceptionally(new RuntimeException("exceptionFuture 沒有執行完"));
        }).start();

        log.debug("(沒執行完就返回0) result:{}", intFuture.get());
        log.debug("(沒執行完就報錯) result:{}",exceptionFuture.get());
    }

返回結果:

18:06:56.179 [main] DEBUG pers.kerry.aservice.service.CFComplete - begin
18:06:59.245 [main] DEBUG pers.kerry.aservice.service.CFComplete - (沒執行完就返回0) result:0
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.RuntimeException: exceptionFuture 沒有執行完
    at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
    at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
    at pers.kerry.aservice.service.CFComplete.main(CFComplete.java:50)
Caused by: java.lang.RuntimeException: exceptionFuture 沒有執行完
    at pers.kerry.aservice.service.CFComplete.lambda$main$2(CFComplete.java:46)
    at java.lang.Thread.run(Thread.java:748)

3. Function方法

Function 方法,是指下面的這類方法的入參都是函式式介面 FunctionBiFunction(Bi是 Bidirectional-雙向 的縮寫,是指有兩個入參的Function)。那麼功能嘛,就和 Stream 裡的 mapflatMap方法類似,返回當前內部資料經過對映轉換的 CompletableFuture。因此它們都是等待上一個 CompletableFuture 完成後執行的。

下面先一一介紹功能,然後再做對比。

3.1. thenApply

thenApply 方法定義為:

public <U> CompletableFuture<U> thenApply(
        Function<? super T,? extends U> fn)

特點為:

  • 入參 Function T -> U,如果前者 CompletableFuture 不報錯,正常完成後,會執行 thenApply 方法。
  • 入參 Function 中沒有 Throwable,故沒有異常處理。如果前者 CompletableFuture 報錯,會直接丟擲異常,並不會執行該方法。

示例程式碼:

    public static CompletableFuture<Integer> divideNumber(int a, int b) {
        return CompletableFuture.supplyAsync(() -> a / b);
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> intFuture = divideNumber(10, 2)
                .thenApply(param -> param * 10);

        CompletableFuture<String> stringFuture = divideNumber(10, 2)
                .thenApply(param -> "這是字串-" + param);

        CompletableFuture<Integer> exceptionFuture = divideNumber(10, 0)
                .thenApply(param -> param * 10);

        log.debug("intFuture result:{}", intFuture.get());
        log.debug("stringFuture result:{}", stringFuture.get());
        log.debug("exceptionFuture result:{}", exceptionFuture.get());
    }

列印日誌為:

22:39:23.478 [main] DEBUG pers.kerry.aservice.service.CFApply - intFuture result:50
22:39:23.485 [main] DEBUG pers.kerry.aservice.service.CFApply - stringFuture result:這是字串-5
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
    at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
    at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
    at pers.kerry.aservice.service.CFApply.main(CFApply.java:36)
Caused by: java.lang.ArithmeticException: / by zero
    at pers.kerry.aservice.service.CFApply.lambda$divideNumber$0(CFApply.java:21)
    at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)
    at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

3.2. exceptionally

exceptionally 方法定義為:

public CompletableFuture<T> exceptionally(
        Function<Throwable, ? extends T> fn)

特點為:

  • 入參 Function Throwable -> T,如果前者 CompletableFuture 不報錯,就不會觸發 exceptionally 方法。
  • 入參 Function 中有 Throwable,因此可以處理異常,前者 CompletableFuture 如果報錯,會觸發該方法,並且返回一個自定義應對錯誤的值。

示例程式碼:

    public static CompletableFuture<Integer> divideNumber(int a, int b) {
        return CompletableFuture.supplyAsync(() -> a / b);
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CompletableFuture<Integer> normalFuture = divideNumber(10, 2)
                .exceptionally((throwable -> {
                    return 0;
                }));

        CompletableFuture<Integer> exceptionFuture = divideNumber(10, 0)
                .exceptionally((throwable -> {
                    return 0;
                }));

        log.debug("normalFuture result:{}", normalFuture.get());
        log.debug("exceptionFuture result:{}", exceptionFuture.get());
    }

列印日誌為:

22:53:34.335 [main] DEBUG pers.kerry.aservice.service.CFExceptionally - normalFuture result:5
22:53:34.342 [main] DEBUG pers.kerry.aservice.service.CFExceptionally - exceptionFuture result:0

3.3. handle

handle 方法定義為:

public <U> CompletableFuture<U> handle(
        BiFunction<? super T, Throwable, ? extends U> fn)

特點為:

  • 入參 BiFunction T,Throwable -> U,無論是否報錯,在執行完成後都會觸發 handle 方法。
  • 入參 Function 中有 Throwable,因此可以處理異常,前者 CompletableFuture 如果報錯,會觸發該方法,並且返回一個自定義應對錯誤的值。

示例程式碼:

public static CompletableFuture<Integer> divideNumber(int a, int b) {
        return CompletableFuture.supplyAsync(() -> a / b);
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> intFuture = divideNumber(10, 2)
                .handle((value, throwable) -> value * 10);

        CompletableFuture<String> stringFuture = divideNumber(10, 2)
                .handle((value, throwable) -> "這是字串-" + value);

        CompletableFuture<Integer> exceptionFuture = divideNumber(10, 0)
                .handle((value, throwable) -> {
                    if (throwable != null) {
                        return 0;
                    }
                    return value * 10;
                });

        log.debug("intFuture result:{}", intFuture.get());
        log.debug("stringFuture result:{}", stringFuture.get());
        log.debug("exceptionFuture result:{}", exceptionFuture.get());
    }

列印日誌為:

23:03:08.771 [main] DEBUG pers.kerry.aservice.service.CFHandle - intFuture result:50
23:03:08.782 [main] DEBUG pers.kerry.aservice.service.CFHandle - stringFuture result:這是字串-5
23:03:08.782 [main] DEBUG pers.kerry.aservice.service.CFHandle - exceptionFuture result:0

3.4. thenCompose

thenCompose 方法定義為:

public <U> CompletableFuture<U> thenCompose(
        Function<? super T, ? extends CompletionStage<U>> fn)

特點為:

  • 入參 Function T -> CompletionStage<U>,如果前者 CompletableFuture 不報錯,正常完成後,會執行 thenCompose 方法。
  • 入參 Function 中沒有 Throwable,故沒有異常處理。如果前者 CompletableFuture 報錯,都會直接丟擲異常,並不會執行該方法。

示例程式碼:

    public static CompletableFuture<Integer> divideNumber(int a, int b) {
        return CompletableFuture.supplyAsync(() -> a / b);
    }

    public static CompletableFuture<Integer> multiplyTen(int a) {
        return CompletableFuture.supplyAsync(() -> a * 10);
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> intFuture = divideNumber(10, 2)
                .thenCompose(CFCompose::multiplyTen);
        CompletableFuture<Integer> exceptionFuture = divideNumber(10, 0)
                .thenCompose(CFCompose::multiplyTen);
        log.debug("intFuture result:{}", intFuture.get());
        log.debug("exceptionFuture result:{}", exceptionFuture.get());
    }

列印日誌為:

23:47:18.967 [main] DEBUG pers.kerry.aservice.service.CFCompose - intFuture result:50
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
    at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
    at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
    at pers.kerry.aservice.service.CFCompose.main(CFCompose.java:48)
Caused by: java.lang.ArithmeticException: / by zero
    at pers.kerry.aservice.service.CFCompose.lambda$divideNumber$0(CFCompose.java:35)
    at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)
    at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

3.5. thenCombine

thenCombine 方法定義為:

public <U,V> CompletableFuture<V> thenCombine(
        CompletionStage<? extends U> other,
        BiFunction<? super T,? super U,? extends V> fn)

特點為:

  • 入參 Function T -> U,如果前者 CompletableFuture 和 CompletionStage<? extends U> other 不報錯,都正常完成後,會執行 thenCombine 方法。
  • 入參 Function 中沒有 Throwable,故沒有異常處理。如果前者 CompletableFuture 或 CompletionStage<? extends U> other 報錯,都會直接丟擲異常,並不會執行該方法。

示例程式碼:

    public static CompletableFuture<Integer> divideNumber(int a, int b) {
        return CompletableFuture.supplyAsync(() -> a / b);
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CompletableFuture<Integer> intFuture = divideNumber(10, 2)
                .thenCombine(divideNumber(10, 5), (v1, v2) -> {
                    return v1 + v2;
                });
        CompletableFuture<Integer> exceptionFuture = divideNumber(10, 2)
                .thenCombine(divideNumber(10, 0), (v1, v2) -> {
                    return v1 + v2;
                });
        log.debug("intFuture result:{}", intFuture.get());
        log.debug("exceptionFuture result:{}", exceptionFuture.get());
    }

列印日誌為:

23:41:52.889 [main] DEBUG pers.kerry.aservice.service.CFCombine - intFuture result:7
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
    at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
    at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
    at pers.kerry.aservice.service.CFCombine.main(CFCombine.java:30)
Caused by: java.lang.ArithmeticException: / by zero
    at pers.kerry.aservice.service.CFCombine.lambda$divideNumber$0(CFCombine.java:16)
    at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)
    at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

3.6. 總結和對比

將上面介紹的幾種方法各自組合起來橫行對比。

1. thenApply、exceptionally、handle 對比

它們的區別在於 Function/BiFunction 的傳入引數上:

  • thenApply: T -> U,只有在不報錯的時候才會執行。
  • exceptionally: Throwable -> T,只有在報錯的時候才會執行。
  • handle: T,Throwable -> U,無論報錯或不報錯的時候,都會執行。

handle 有點像是把 thenApply、exceptionally 結合起來了。

2. thenApply、thenCompose 對比

它們都是隻有在不報錯的時候才會執行,最終也是返回 CompletableFuture,但區別在於 Function 中的返回引數型別:

  • thenApply: Function<? super T,? extends U> fn)
  • thenCompose: Function<? super T, ? extends CompletionStage<U>> fn)

因此可以類比於 Stream 中 map 和 flatMap 方法的區別,它們 Function 中的返回引數型別:

  • map: Function<? super T, ? extends R> mapper
  • flatMap: Function<? super T, ? extends Stream<? extends R>
3. thenCombine

很遺憾,thenCombine 在這個分類裡面沒有同行可以對比,它的功能在於結合兩個 ComletableFuture 的值,最終返回一個新的 ComletableFuture。它的橫向對比應該在 allOf。只不過因為屬於 Function 的範疇,就放進來了。

4. Consumer方法

Consumer 方法,是指下面的這類方法的入參都是函式式介面 ConsumerBiConsumer。也都是等待上一個 CompletableFuture 完成後執行的。

4.1. thenAccept

thenAccept 方法定義為:

public CompletableFuture<Void> thenAccept(Consumer<? super T> action)

特點為:

  • 入參 Consumer T,如果前者 CompletableFuture 不報錯,正常完成後,會執行 thenAccept 方法。
  • 入參 Consumer 中沒有 Throwable,故沒有異常處理。如果前者 CompletableFuture 報錯,會直接丟擲異常,並不會執行該方法。

示例程式碼:

    public static CompletableFuture<Integer> divideNumber(int a, int b) {
        return CompletableFuture.supplyAsync(() -> a / b);
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Void> intFuture = divideNumber(10, 2)
                .thenAccept((value) -> {
                    log.debug("intFuture result:{}", value);
                });

        CompletableFuture<Void> exceptionFuture = divideNumber(10, 0)
                .thenAccept((value) -> {
                    log.debug("exceptionFuture result:{}", value);
                });

        Thread.sleep(1000);
    }

列印日誌為:

00:15:21.228 [main] DEBUG pers.kerry.aservice.service.CFAccept - intFuture result:5

4.2. whenComplete

whenComplete 方法定義為:

public CompletableFuture<T> whenComplete(
        BiConsumer<? super T, ? super Throwable> action)

特點為:

  • 入參 BiConsumer T,Throwable -> U,無論是否報錯,在執行完成後都會觸發 whenComplete 方法。
  • 入參 BiConsumer 中有 Throwable,因此可以處理異常,前者 CompletableFuture 如果報錯,會觸發該方法。

示例程式碼:

    public static CompletableFuture<Integer> divideNumber(int a, int b) {
        return CompletableFuture.supplyAsync(() -> a / b);
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> intFuture = divideNumber(10, 2)
                .whenComplete((value, throwable) -> {
                    log.debug("whenComplete value:{}", value);
                });

        CompletableFuture<Integer> exceptionFuture = divideNumber(10, 0)
                .whenComplete((value, throwable) -> {
                    if (throwable != null) {
                        log.error("whenComplete 出錯啦");
                    }
                });

        log.debug("intFuture result:{}", intFuture.get());
        log.debug("exceptionFuture result:{}", exceptionFuture.get());
    }

列印日誌為:

00:19:27.738 [main] DEBUG pers.kerry.aservice.service.CFWhen - whenComplete value:5
00:19:27.746 [main] ERROR pers.kerry.aservice.service.CFWhen - whenComplete 出錯啦
00:19:27.746 [main] DEBUG pers.kerry.aservice.service.CFWhen - intFuture result:5
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
    at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
    at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)
    at pers.kerry.aservice.service.CFWhen.main(CFWhen.java:37)
Caused by: java.lang.ArithmeticException: / by zero
    at pers.kerry.aservice.service.CFWhen.lambda$divideNumber$0(CFWhen.java:20)
    at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)
    at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

4.3. 總結對比

1. thenAccept、whenComplete 對比

它們的區別包含 Function 的入參,和方法的返回值:

  • thenAccept: 入參:T,只有在不報錯的時候才會執行。返回 CompletableFuture<Void>,如果再用 get() 只有 null。
  • whenComplete: 入參:Throwable,T,無論報錯或不報錯的時候,都會執行。返回 CompletableFuture<T>,即 執行 whenComplete 方法前的原 CompletableFuture<T>。

在 Function 入參上,它們的區別和前面的 thenApply、exceptionally、handle 類似。有人說,那這裡怎麼就沒有入參只為 Throwable 的方法呢?仔細想想 whenComplete 就夠用了。

其他 thenRun

前面都是有入引數的,如果後續的方法依然依賴前者,而且不需要入餐,可以試試 thenRun

5. 並行處理方法

前面說的 Function、Consumer 都是有先後執行關係,因為後面的任務依賴於前面的任務的結果。這章來聊聊並行任務的處理方法。

5.1. 兩個並行

5.1.1. both

兩個任務,如果我們想讓它們都執行完成,如果使用傳統的 future的話,我們通常是這麼寫的:

Future<String> futureA = executorService.submit(() -> "resultA");
Future<String> futureB = executorService.submit(() -> "resultB");
String resultA = futureA.get();
String resultB = futureB.get();

但其實就在上述介紹的方法裡,除了已經說過的 thenCombine,還有很多 both 方法的變種能實現:

CompletableFuture<String> cfA = CompletableFuture.supplyAsync(() -> "resultA");
CompletableFuture<String> cfB = CompletableFuture.supplyAsync(() -> "resultB");

cfA.thenAcceptBoth(cfB, (resultA, resultB) -> {});
cfA.thenCombine(cfB, (resultA, resultB) -> "result A + B");
cfA.runAfterBoth(cfB, () -> {});

5.1.2. either

上述的問題,兩個任務,如果我們想讓它們只要有任意一個執行完成就可以了,怎麼實現呢?

cfA.acceptEither(cfB, result -> {});
cfA.acceptEitherAsync(cfB, result -> {});
cfA.acceptEitherAsync(cfB, result -> {}, executorService);

cfA.applyToEither(cfB, result -> {return result;});
cfA.applyToEitherAsync(cfB, result -> {return result;});
cfA.applyToEitherAsync(cfB, result -> {return result;}, executorService);

cfA.runAfterEither(cfA, () -> {});
cfA.runAfterEitherAsync(cfB, () -> {});
cfA.runAfterEitherAsync(cfB, () -> {}, executorService);

上面的各個帶 either 的方法,表達的都是一個意思,指的是兩個任務中的其中一個執行完成,就執行指定的操作。它們幾組的區別也很明顯,分別用於表達是否需要任務 A 和任務 B 的執行結果,是否需要返回值。

5.2. 多個並行

如果只考慮兩個任務的並行,太侷限了,這裡來考慮任意多個任務的並行情況,allOfanyOf 方法:

public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs){...}
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) {...}

這兩個方法都非常簡單,下面是 allOf 的例子:

CompletableFuture cfA = CompletableFuture.supplyAsync(() -> "resultA");
CompletableFuture cfB = CompletableFuture.supplyAsync(() -> 123);
CompletableFuture cfC = CompletableFuture.supplyAsync(() -> "resultC");

CompletableFuture<Void> future = CompletableFuture.allOf(cfA, cfB, cfC);
// 所以這裡的 join() 將阻塞,直到所有的任務執行結束
future.join();

由於 allOf 聚合了多個 CompletableFuture 例項,所以它是沒有返回值的。這也是它的一個缺點。

anyOf 也非常容易理解,就是隻要有任意一個 CompletableFuture 例項執行完成就可以了,看下面的例子:

CompletableFuture cfA = CompletableFuture.supplyAsync(() -> "resultA");
CompletableFuture cfB = CompletableFuture.supplyAsync(() -> 123);
CompletableFuture cfC = CompletableFuture.supplyAsync(() -> "resultC");

CompletableFuture<Object> future = CompletableFuture.anyOf(cfA, cfB, cfC);
Object result = future.join();

最後一行的 join() 方法會返回最先完成的任務的結果,所以它的泛型用的是 Object,因為每個任務可能返回的型別不同。

相關文章