前言:要寫一手優雅的代碼,背後要經曆很多的積累和沉澱,有點類似台上一分鐘,台下十年功.要讓代碼變得優雅有很多種方式,使用JDK8提供的新特性便是其中一種,雖然相較于設計模式對代碼品質提升有限,但仍值得去學習和使用.本篇僅介CompletableFuture.
早在JDK1.5中就提供了Future和Callable來擷取異步任務的結果,但因為擷取結果阻塞的原因,并沒有真正實作異步帶來的價值.體驗非常不好,于是JDK在1.8中終于出了Future的增強版CompletableFuture,借助CompletableFuture提供的異步能力和lambda表達式風格,可以寫出優雅的異步程式設計代碼.
目錄
1.建立CompletableFuture對象
2.同步擷取計算結果
3.計算完成後的處理
4.處理多階段任務
5.組合任務
6.Tips
7.代碼示範
8.總結
1.建立CompletableFuture對象
CompletableFuture提供了4個靜态方法用于建立CompletableFuture執行個體:
CompletableFuture<Void> runAsync(Runnable runnable)
CompletableFuture<Void> runAsync(Runnable runnable,Executor executor)
CompletableFuture<U> supplyAsync(Supplier<U> supplier)
CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor)
其中帶Executor參數的用于指定自定義異步執行線程池,如果不指定則預設使用JDK提供的FrokJoinPool.commonPool().
如果無需關心執行後的結果,使用runAsync建立即可,否則使用supplyAsync建立CompletableFuture.
2.同步擷取計算結果
CompletableFuture提供4種用于同步等待擷取計算結果的方法,這點跟老的Future沒啥兩樣,隻不過額外提供了getNow和join方法.
public T get()
public T get(long timeout, TimeUnit unit)
public T getNow(T valueIfAbsent)
public T join()
getNow用于立即擷取傳回結果,如果此時被調用方法還沒傳回值,則傳回指定的valueIfAbsent.
join方法和get作用一樣,用于阻塞擷取執行結果,不同的是join在調用方法出現異常時僅會傳回uncheckedException,而get則傳回具體異常.
3.計算完成後的處理
CompletableFuture可以通過whenComplete/whenCompleteAsync來擷取計算結果并處理,不再需要阻塞擷取,而是以異步通知回調的方式.
public CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)
4.處理多階段任務
如果一個任務需要多個階段才能最終完成,那麼可以采用thenApply這種方式鍊式調用完成整個任務.
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)
也可以采用handle方式:
public <U> CompletableFuture<U> handle(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> handleAsync(BiFunction<? super T,Throwable,? extends U> fn, Executor executor)
與thenApply不同的是,handle是在任務完成後再執行,且可以處理異常,thenApply遇到異常則會抛出.
如果任務無需傳回,隻需要處理結果,那麼可以使用消費者
public CompletableFuture<Void> thenAccept(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action)
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor)
5.組合任務
thenAcceptBoth用于組合兩個任務,當兩個任務都計算完成時執行:
public <U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other,
BiConsumer<? super T, ? super U> action)
public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,
BiConsumer<? super T, ? super U> action)
thenAcceptEither隻要兩個任務中有一個先完成了即執行:
public <U> CompletableFuture<U> applyToEither(
CompletionStage<? extends T> other, Function<? super T, U> fn)
如果要組合的任務超過2個,可以用:
任意一個完成:
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)
全部完成:
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)
6.Tips
JDK8CompletableFuture API中提供了五十多個靜态方法,乍看上去很多,其實實際開發中經常被用到的連一半都不到,再加上大部分其實是方法重載,以及功能類似方法,抽象一下最後隻需要掌握十個以内的方法,便可以駕馭CompletableFuture寫出優雅代碼.
建立CompletableFuture對象時,以run打頭的方法無傳回值,以supply打頭的方法有傳回,可以對傳回結果進一步處理.
處理任務時,方法名中包含Apply的方法有傳回值,是生産者,方法名中包含Accept的方法無傳回值,是消費者.
以Async結尾的方法表明該方法異步執行.
7.代碼示範
上面都是純純的理論部分,并沒有結合實際代碼示範,是以下面正式結合實際代碼示範之:
幾種經常會用到場景如下所示,如果不需要處理傳回結果,可以将Apply替換為Accept作為純消費者即可
public class Client {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//case1:任務需要多個階段才能完成,完成執行後續動作...
CompletableFuture.supplyAsync(() -> "hello")
.thenApplyAsync(s -> s + " ")
.thenApplyAsync(s -> s + "world")
.thenApplyAsync(String::toUpperCase)
.whenCompleteAsync((result, throwable) -> System.out.println(result));
//case2:完成任務需要調用rpc接口,有很多個rpc接口,隻要其中一個調用成功即算完成任務
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
try {
//模拟調用延遲
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "world";
});
//省略其它rpc接口...
CompletableFuture<Object> res = CompletableFuture.anyOf(future1, future2);
System.out.println(res.get());
//case3:完成任務需要所有的rpc接口都完成才算完成
//如果使用allOf是沒有傳回結果的
CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2);
System.out.println(all.get());
//是以如果需要擷取最終傳回的結果,需要手動處理異步傳回的結果
String combine = Stream.of(future1, future2)
.map(CompletableFuture::join)
.collect(Collectors.joining());
System.out.println(combine);
}
}
運作結果:
8.總結
CompletableFuture封裝的真的好,以至于我在寫上面這段代碼時都不想寫下去了,真的太簡單了,沒啥好示範的,隻要有jdk8的lambda基礎和functional接口基礎,就可以輕松上手,之是以寫這篇也是為了做個筆記,總結下然後加深印象,感謝你的閱讀,文中若有不正之處,歡迎留言斧正。