1. 前言
剛工作時,對於非同步程式設計的觀念,可能停留線上程、執行緒池的概念。建立一個新執行緒去執行,只執行用 Runnable
,想要有返回值就用 Callable
返回 Future
。再有經驗一點的,用執行緒池來管理執行緒。但這些都停留在基礎階段,到實際開發時會遇到各種複雜的應用場景,雖然併發包裡面也提供了一些像 Semaphore
、CountDownLatch
、CyclicBarrier
的協作類,但還是不夠,這裡給大家介紹 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 方法,是指下面的這類方法的入參都是函式式介面 Function
或 BiFunction
(Bi是 Bidirectional-雙向 的縮寫,是指有兩個入參的Function)。那麼功能嘛,就和 Stream 裡的 map
、flatMap
方法類似,返回當前內部資料經過對映轉換的 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 方法,是指下面的這類方法的入參都是函式式介面 Consumer
或 BiConsumer
。也都是等待上一個 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. 多個並行
如果只考慮兩個任務的並行,太侷限了,這裡來考慮任意多個任務的並行情況,allOf
和 anyOf
方法:
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,因為每個任務可能返回的型別不同。