深入學習java8?中的CompletableFuture
1 前言
在項目開發(fā)中,異步化處理是非常常見的解決問題的手段,異步化處理除了使用線程池之外,還可以使用 CompletableFuture
來實現,在多任務處理且之間存在邏輯關系的情況下,就能夠體現出其巨大的優(yōu)勢和靈活性。CompletableFuture
底層使用的是 ForkJoinPool
線程池來實現線程的執(zhí)行和調度。
2 簡單使用
在使用線程池時,通常的使用方法如下所示:
ExecutorService service = Executors.newFixedThreadPool(3); Callable<String> task1 = () ->{return "task1";}; Runnable task2 = () ->{ System.out.println("task2 "); }; // 用于提交任務根據是否獲取返回值分為 Callable 和 Runnable,分別使用 submit 和 execute 方法 service.submit(task1); service.execute(task2);
但是在 CompletableFuture
中,使用方法還是有所區(qū)別的,是線程池和任務的結合,能夠使用鏈式編程來處理任務之間的邏輯關系。
具體的使用如下所示:
// 使用默認線程池 CompletableFuture<String> async1 = CompletableFuture.supplyAsync(() -> { log.info("async1 ... "); return "async1"; }); // 使用自定義線程池 CompletableFuture<String> async1 = CompletableFuture.supplyAsync(() -> { log.info("async1 ... "); return "async1"; }, Executors.newSingleThreadExecutor()); // runAsync 的使用方式 CompletableFuture<Void> future = CompletableFuture.runAsync(()-> { System.out.println("runAsync"); });
異步任務的開啟一般有兩個方法,supplyAsync
和 runAsync
,這兩個方法的別別在于:
- 1 supplyAsync 不接受入參,但是會有返回結果。
- 2 runAsync 也是不接受入參,但是沒有返回結果。
這里需要先說明一下,xxxAsync 的方法都是從使用線程池中獲取一個線程來處理任務,不帶 Async 結尾的方法則是使用上一任務的線程繼續(xù)處理。
3 異步處理
在正式開始之前,需要講解一下 java8 函數式編程的函數,相信大家看到這么多的函數都會頭暈的,但是其中也是有規(guī)律可循的,先說三個主要的:
- 1 Function , 既然是函數,那么就會有一個入參和返回值,可以用于計算。
- 2 Comsumer , 是一個消費者,接收一個入參但是沒有返回值,只用于消費。
- 3 Supplier, 是一個提供者,不接受參數,但是有一個返回值,可以用于對象的創(chuàng)建。
- 4 Predicate, 用來做判斷使用,接收一個入參,返回值是 布爾類型的,true 或者 false。
簡單的案例如下圖所示:
有這基本的 4 個,就可以進行延伸了,比如 IntFunction 則是接收一個 int 類型的參數,處理完成后即可返回,前面的 Int 只是規(guī)定了入參的類型而已,再有 BiConsumer , 則是接收兩個入參,Consumer 則是只能接收一個參數。依次類推就可以知道所有的函數式接口的功能,是不是很簡單?
3.1 thenApply
thenApply 和 thenApplyAsync 都是接收一個 Function 參數,即接收一個參數并返回結果。區(qū)別在于前者是使用前一個任務的線程繼續(xù)處理,后者是從線程池中在獲取一個線程處理任務。
如上圖所示,thenApply 的任務處理和 future 使用的是一個線程,但是 thenApplyAsync 就換了一個線程繼續(xù)數據的處理。
3.2 thenAccept 和 thenRun
從方法名可以看到 thenAccept 和 thenRun 都是使用前一個人任務的線程進行處理的。兩者都是在前一個任務完成后進行處理,區(qū)別點在于 thenAccept 接收的是一個 Consumer , 而 thenRun 接收的是一個 Runnable, 因此兩者都沒有返回值,但是前者可以接收并消費一個參數,但是 thenRun 不能接收參數。這兩個方法的測試如下圖所示:
既然這兩個方法已經搞清楚了,那么 thenAcceptAsync 和 thenRunAsync 是不是就順手學到了呢?異步編程的 API 真的是很簡單。
3.3 exceptionally 異常處理
exceptionally 屬于異常處理流程,如果發(fā)生異常則需要進行異常處理,需要將異常最為參數傳遞給 exceptionally, 而其需要的是一個 Function 參數,這里的異常處理也是同步進行的,也是采用上一個任務的線程進行處理。
// 拋出異常信息 CompletableFuture<String> exceptionally = future.exceptionally((ex) -> { log.info("error information " + ex.getLocalizedMessage()); return ex.getMessage(); });
3.4 whenComplete 方法完成之后
這個方法是當某個任務執(zhí)行完成之后進行回調,會將任務的執(zhí)行結果或者執(zhí)行期間的異常信息傳遞過來進行處理,在正常的情況下,異常信息為 null,能夠得到任務的運算結果,異常情況下,異常信息不為空,返回結果為 null。這里的 whenComplete 接受的是一個 BiConsumer 函數,也就是兩個入參,沒有返回結果,一個是方法的返回結果,一個則是任務處理過程中的異常信息。
// 返回結果 CompletableFuture<String> whenComplete = future.whenComplete((res, ex) -> { if (StrUtil.isNotBlank(res)) { log.info("task execute result {}", res); } if (res != null) { log.info("task error info {}", ex.getMessage()); } });
知道了 whenComplete 方法,那么 whenCompleteAsync 方法的使用就知道了,就是異步處理了。
3.5 handle
handle 的使用和 whenComplete 方法類似,都是獲取任務的結果,只不過 handle 有返回結果,接受的參數是一個 BiFunction ,那么具體的使用方法如下圖所示:
// handle 處理返回結果 CompletableFuture<String> handle = future.handle((res, ex) -> { if (StrUtil.isNotBlank(res)) { log.info("task execute result {}", res); return "handle result exception"; } if (res != null) { log.info("task error info {}", ex.getMessage()); } return "handle result"; });
通過以上的分析,我們已經到得了以下規(guī)律:任何一個方法的實現都有三個類似的 API,一個是同步處理,一個是異步處理,一個是異步處理并指定線程池參數。目前已經介紹了 6 個 API,分別是 thenApply
, thenAccept
,thenRun
, whenComplete
, handle
和 一個異常處理 exceptionally
, 前五個舉一反三就知道了其他的兩個異步調用 API,掌握了其中的規(guī)律就不會覺得很多,無非就是同步異步,是否接收參數和有無返回值的區(qū)別。
4 處理組合
4.1 任務均完成后組合
thenCombine
、thenAcceptBoth
、runAfterBoth
這三個方法都是在兩個 CompletableFuture
任務結束后在進行執(zhí)行,區(qū)別在于是否接受參數以及是否有返回值,如圖所示查看其接受的參數。
- 1
thenCombine
方法為兩個,第一個為CompletionStage
對象即另一個異步任務,第二個為BiFunction
,接收兩個任務的處理結果并返回處理結果。 - 2
thenAcceptBoth
方法為兩個,第一個為CompletionStage
對象即另一個異步任務,第二個為BiConsumer
, 接收兩個任務的處理結果不過沒有返回值。 - 3
runAfterBoth
方法為兩個,第一個為CompletionStage
對象即另一個異步任務,第二個為Runnable
,不接收兩個任務的處理結果,也沒有返回值。
下圖是方法的使用案例:
既然知道了這些方法的用法,那么
thenCombineAsync
、thenAcceptBothAsync
、runAfterBothAsync
是不是就可以同理掌握了呢?
4.2 任一任務完成
前文提到的都是兩個任務均完成的情況,接下來的三個方法則是任何一個任務完成即可執(zhí)行下一個動作,applyToEither
、acceptEither
、runAfterEither
這三個方法都是在兩個異步任務執(zhí)行結果之后的處理,任何一個任務執(zhí)行完畢之后就進行繼續(xù)處理。
這里的任一任務執(zhí)行完成和兩者任務都執(zhí)行完在執(zhí)行是類似的,區(qū)別在于這里接收的是一個參數:
- 1 applyToEither 接收的參數是 CompletionStage 和 Function。
- 2 acceptEither 接收的參數是 CompletionStage 和 Consumer。
- 3 runAfterEither 接收的參數是 CompletionStage 和 Runnable。
這里已經學習到了 applyToEither
、acceptEither
、runAfterEither
三個方法,那么類似的 applyToEitherAsync
、acceptEitherAsync
、runAfterEitherAsync
也可以知道其具體用法。
4.3 任務處理結果
thenCompose
的用法和 thenCombine
等的用法基本都是一樣的,只不過在返回參數上有所區(qū)別,結果是返回一個 Future, 入參是一個 Function 。在了解了 thenCompose
之后,那么 thenComposeAsync
的使用方法就是類似了。
CompletableFuture<String> thenCompose = future.thenCompose((res) -> { log.info("result is {}", res); return CompletableFuture.supplyAsync(() -> { log.info("supplyAsync"); return "result"; }); });
4.4 所有或者任何
前面已經分享過了兩個任務和一個任務的處理之后的操作,在本節(jié)中將分享 allOf
和 anyOf
,這是多個任務的聚合處理,入參都是多個 CompletableFuture
, 區(qū)別在于是任何一個任務完成后就執(zhí)行后續(xù)任務,還是所有的任務都完成后再繼續(xù)任務處理。
其使用方法如下所示:
CompletableFuture<Void> allOf = CompletableFuture.allOf(future); CompletableFuture<Object> andOf = CompletableFuture.anyOf(future);
5 總結
文中,我們首先介紹了函數式編程的接口使用方法,然后分享了 CompletableFuture
的 API 使用方法。核心就是函數式編程接口,接收的是 Function
、Consumer
還是 Runable
, 其次就是否是 xxxAsync
異步處理。
到此這篇關于深入學習java8 中的CompletableFuture的文章就介紹到這了,更多相關java8 CompletableFuture 內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringBoot使用攔截器Interceptor實現統一角色權限校驗
角色權限校驗,是保證接口安全必備的能力:有權限才可以操作,所以,一般對于這種通用邏輯,推薦不與主業(yè)務邏輯耦合,那么怎么來解耦,那么本文小編就給大家詳細講解如何使用攔截器Interceptor實現統一角色權限校驗,需要的朋友可以參考下2023-07-07在springboot中注入FilterRegistrationBean不生效的原因
這篇文章主要介紹了在springboot中注入FilterRegistrationBean不生效的原因及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08