Java CompletableFuture使用方式
在我之前的文章IO密集型服務提升性能的三種方法中提到過,提升IO密集型應用性能有個方式就是異步編程,實現(xiàn)異步時一定會用到Future,使用多線程+Future我們可以讓多個任務同時去執(zhí)行,最后統(tǒng)一去獲取執(zhí)行結果,這樣整體執(zhí)行的時長就取決于最長的一個任務,比如如下代碼:
public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(2); System.out.println(new Date()); Future<String> future1 = executorService.submit(() -> { Thread.sleep(5000); return new Date() + ":" + "thread1 result"; }); Future<String> future2 = executorService.submit(() -> { Thread.sleep(3000); return new Date() + ":" + "thread2 result"; }); try { System.out.println(future1.get()); System.out.println(future2.get()); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { throw new RuntimeException(e); } }
上面代碼的運行時長是5s,如果兩個任務串行執(zhí)行的化,運行時間就會是8s,當多個任務多線程異步執(zhí)行時,最終總的執(zhí)行時長取決于最長任務的執(zhí)行時長。Future在大部分異步變成情況下,已經能很好的滿足我們異步變成的訴求了,但當我看到CompletableFuture這個東西的時候,才發(fā)現(xiàn)Future還是太簡單,我還是太年輕。
CompletableFuture除了和Future一樣可以獲取執(zhí)行結果外,它還**簡化了異常、提供了手動設置結果的接口、鏈式操作、結果組合、回調,**接下來我們通過一些代碼示例,來看下CompletableFuture這些特性如何吊打Future。
異常處理
在使用Future時,你只能在get()的時候抓取到異常,異常處理會和結果獲取的邏輯混在一起,有些時候處理起來會比較麻煩,而CompletableFuture提供了exceptionally
和handle
兩個api,可以很方便的添加異常處理邏輯,后續(xù)只需要直接使用get獲取結果即可,代碼示例如下:
public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(2); CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { // 故意拋出一個異常 if (System.currentTimeMillis() % 2 == 0) { throw new RuntimeException("Exception occurred!"); } return "Result"; }, executorService); future = future.exceptionally(ex -> { // 對異常進行處理,并提供一個異常時的結果 return "Default Value"; }); try { System.out.println(future.get()); // 輸出 "Default Value" } catch (Exception e) { e.printStackTrace(); } }
exceptionally api讓任務在運行過程中,如果抓到了Exception,就可以調用你給定的邏輯來處理異常。
與exceptionally就是handle方法,達到的效果一致,但使用略有不同,可以看出handle這個api是在任務執(zhí)行完成后,講exception和執(zhí)行結果共同處理一次。
future = future.handle((result, ex) -> { if (ex != null) { return "Default Value"; } return result; }); try { System.out.println(future.get()); // 輸出 "Default Value" } catch (Exception e) { e.printStackTrace(); }
手動設置結果
在Future
中,我們無法手動地完成一個任務或者設置任務的結果,這個任務的執(zhí)行結果完全取決于任務的執(zhí)行情況。
但在CompletableFuture
,我們可以在任何時間點上手動設置結果或者標記任務為已完成。
舉個例子,我要通過接口獲取某個數(shù)據,但很要求時效性,如果超過1s我就不要接口調用結果了,而是返回一個默認值。
Future設置等待時間也可以實現(xiàn)類似的功能,但不是很直觀, CompletableFuture
就很簡單直觀了。
public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(2); CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { try { // 隨機sleep 0-2s Thread.sleep(ThreadLocalRandom.current().nextInt(2000)); } catch (InterruptedException e) { throw new RuntimeException(e); } return "Result"; }, executorService); CompletableFuture<String> finalFuture = future; // 1s后手動完成 executorService.submit(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 手動地完成這個 CompletableFuture,并設置計算結果 finalFuture.complete("Default Value"); }); try { System.out.println(future.get()); // 概率性輸出 "Default Value" } catch (Exception e) { e.printStackTrace(); } }
鏈式操作
鏈式操作意味著你可以將一個CompletableFuture
的結果直接傳遞給另一個異步操作,而無需等待第一個操作完成。
這使得你可以更容易地編寫復雜的并發(fā)邏輯,這種非常適合需要將多個接口結果串起來的流程,有點像langChain,我們直接看示例代碼:
public static void main(String[] args) { CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { // 第一個異步任務 return "Hello"; }).thenApplyAsync(result -> { // 第二個異步任務,依賴第一個任務的結果 return result + " World"; }).thenApplyAsync(result -> { // 第三個異步任務,依賴第二個任務的結果 return result + "!"; }); try { System.out.println(future.get()); } catch (InterruptedException e) { throw new RuntimeException(e); } catch (ExecutionException e) { throw new RuntimeException(e); } }
這個示例比較簡單,在實際使用中,我們可以將三個任務替換成任何需要相互依賴的任務,你只需要定義每個任務的輸入和輸出,然后將它們連接在一起,就可以創(chuàng)建一個復雜的并發(fā)流程。
結果組合
CompletableFuture
提供了幾個方法來組合或合并多個CompletableFuture
的結果。
最常用的可能是thenCombine
和allOf
方法。
以下是thenCombine
方法的使用示例:
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> { return "Hello"; }); CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> { return "World!"; }); CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (s1, s2) -> { return s1 + ", " + s2; }); try { String result = combinedFuture.get(); System.out.println(result); // 輸出 "Hello, World!" } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); }
在上述代碼中,我們首先創(chuàng)建了兩個獨立的CompletableFuture
,然后使用thenCombine
方法將它們的結果組合在一起。
以下是allOf
方法的使用示例:
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> { return "Hello"; }); CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> { return "World!"; }); CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2); // 當所有的 CompletableFuture 都完成時,獲取結果 allFutures.thenRun(() -> { try { String result1 = future1.get(); String result2 = future2.get(); System.out.println(result1 + ", " + result2); // 輸出 "Hello, World!" } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } });
可以看出thenCombine和allOf都實現(xiàn)了類似的功能,但兩者的側重點不同,thenCombine實現(xiàn)的是對執(zhí)行結果的聚合,而allOf實現(xiàn)的是對執(zhí)行完狀態(tài)的聚合,還是需要你去顯式調用get()方法去獲取結果的,所以你還是得根據具體需求來選擇使用thenCombine
還是allOf
。
回調
使用Future時,我們只能通過get()或者isDone()方法來獲取到任務是否執(zhí)行完成了,無法讓任務執(zhí)行完成后主動通知到我們,但CompletableFuture提供了一些回調方法,可以讓任務執(zhí)行完成后執(zhí)行結果處理的任務或者執(zhí)行一些通知的邏輯。
CompletableFuture
提供了幾個方法來設置回調,如thenApply
, thenAccept
, 和 thenRun
。
具體看代碼示例:
public static void main(String[] args) { CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> { // 異步任務 return "Hello, World!"; }); future.thenApply(result -> { // 當 future 完成時,這個回調會被調用。 // result 參數(shù)是 future 的結果。 // 這個回調的結果會被包裝成一個新的 CompletableFuture。 return result.length(); }).thenAccept(length -> { // 當前面的 CompletableFuture 完成時,這個回調會被調用。 // length 參數(shù)是前面 CompletableFuture 的結果。 // 這個回調沒有返回值。 System.out.println(length); // 輸出 "Hello, World!" 的長度 }).thenRun(() -> { // 當前面的 CompletableFuture 完成時,這個回調會被調用。 // 這個回調沒有輸入參數(shù),也沒有返回值。 System.out.println("Done"); }); }
thenApply、thenAccept和thenRun都是在任務執(zhí)行完成后被調起,但他們定位有所不同,thenApply處理的結果可以被包裝成一個新的CompletableFuture,只后可以繼續(xù)鏈式調用。
但thenAccept只能接受輸入,無法提供輸出,所以他的定位是任務的收尾,比如可以將結果輸出。
而thenRun既沒有輸入也沒有輸出,所以它只是獲取到了任務執(zhí)行完的狀態(tài),任務的執(zhí)行結果是獲取不到的,所以它是最弱的。
總結
總結下本文,Future
盡管有用,但在功能上還是相對簡單。
CompletableFuture
不僅提供了獲取執(zhí)行結果的能力,它還增添了異常處理、手動完成、鏈式操作、結果組合和回調等強大功能。
- 異常處理: 利用
exceptionally
和handle
方法,可以簡化異常處理流程,使其更加直觀和易于管理。 - 手動設置結果:
CompletableFuture
允許我們在任意時間點手動設置結果,增加了靈活性。 - 鏈式操作: 通過鏈式操作,我們可以將不同的異步操作以流水線的方式串聯(lián)起來,編寫復雜的并發(fā)邏輯變得更加簡單。
- 結果組合:
thenCombine
和allOf
方法能夠將多個CompletableFuture
的結果合并,為我們提供了更多處理異步操作結果的方式。 - 回調: 提供了
thenApply
,thenAccept
, 和thenRun
等方法,允許我們在任務完成后進行結果處理或其他邏輯操作。
通過這些特性,CompletableFuture
顯著地提升了異步編程的能力和靈活性。無論是簡化異常處理,還是實現(xiàn)復雜的異步邏輯,CompletableFuture
都能夠幫助我們寫出更清晰、更高效的代碼。我相信通過本文的示例代碼,你可以更深入地理解CompletableFuture
的強大之處,并在你的項目中充分利用它來提升性能。
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
SpringBoot使用Shiro實現(xiàn)動態(tài)加載權限詳解流程
本文小編將基于?SpringBoot?集成?Shiro?實現(xiàn)動態(tài)uri權限,由前端vue在頁面配置uri,Java后端動態(tài)刷新權限,不用重啟項目,以及在頁面分配給用戶?角色?、?按鈕?、uri?權限后,后端動態(tài)分配權限,用戶無需在頁面重新登錄才能獲取最新權限,一切權限動態(tài)加載,靈活配置2022-07-07詳解SpringBoot+Dubbo集成ELK實戰(zhàn)
這篇文章主要介紹了詳解SpringBoot+Dubbo集成ELK實戰(zhàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-10-10mybatis mapper互相引用resultMap啟動出錯的解決
這篇文章主要介紹了mybatis mapper互相引用resultMap啟動出錯的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08