Java CompletableFuture使用方式
在我之前的文章IO密集型服務(wù)提升性能的三種方法中提到過,提升IO密集型應(yīng)用性能有個方式就是異步編程,實現(xiàn)異步時一定會用到Future,使用多線程+Future我們可以讓多個任務(wù)同時去執(zhí)行,最后統(tǒng)一去獲取執(zhí)行結(jié)果,這樣整體執(zhí)行的時長就取決于最長的一個任務(wù),比如如下代碼:
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,如果兩個任務(wù)串行執(zhí)行的化,運行時間就會是8s,當(dāng)多個任務(wù)多線程異步執(zhí)行時,最終總的執(zhí)行時長取決于最長任務(wù)的執(zhí)行時長。Future在大部分異步變成情況下,已經(jīng)能很好的滿足我們異步變成的訴求了,但當(dāng)我看到CompletableFuture這個東西的時候,才發(fā)現(xiàn)Future還是太簡單,我還是太年輕。
CompletableFuture除了和Future一樣可以獲取執(zhí)行結(jié)果外,它還**簡化了異常、提供了手動設(shè)置結(jié)果的接口、鏈?zhǔn)讲僮?、結(jié)果組合、回調(diào),**接下來我們通過一些代碼示例,來看下CompletableFuture這些特性如何吊打Future。
異常處理
在使用Future時,你只能在get()的時候抓取到異常,異常處理會和結(jié)果獲取的邏輯混在一起,有些時候處理起來會比較麻煩,而CompletableFuture提供了exceptionally 和handle 兩個api,可以很方便的添加異常處理邏輯,后續(xù)只需要直接使用get獲取結(jié)果即可,代碼示例如下:
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 -> {
// 對異常進行處理,并提供一個異常時的結(jié)果
return "Default Value";
});
try {
System.out.println(future.get()); // 輸出 "Default Value"
} catch (Exception e) {
e.printStackTrace();
}
}exceptionally api讓任務(wù)在運行過程中,如果抓到了Exception,就可以調(diào)用你給定的邏輯來處理異常。
與exceptionally就是handle方法,達到的效果一致,但使用略有不同,可以看出handle這個api是在任務(wù)執(zhí)行完成后,講exception和執(zhí)行結(jié)果共同處理一次。
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();
}手動設(shè)置結(jié)果
在Future中,我們無法手動地完成一個任務(wù)或者設(shè)置任務(wù)的結(jié)果,這個任務(wù)的執(zhí)行結(jié)果完全取決于任務(wù)的執(zhí)行情況。
但在CompletableFuture,我們可以在任何時間點上手動設(shè)置結(jié)果或者標(biāo)記任務(wù)為已完成。
舉個例子,我要通過接口獲取某個數(shù)據(jù),但很要求時效性,如果超過1s我就不要接口調(diào)用結(jié)果了,而是返回一個默認值。
Future設(shè)置等待時間也可以實現(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,并設(shè)置計算結(jié)果
finalFuture.complete("Default Value");
});
try {
System.out.println(future.get()); // 概率性輸出 "Default Value"
} catch (Exception e) {
e.printStackTrace();
}
}鏈?zhǔn)讲僮?/h2>
鏈?zhǔn)讲僮饕馕吨憧梢詫⒁粋€CompletableFuture的結(jié)果直接傳遞給另一個異步操作,而無需等待第一個操作完成。
這使得你可以更容易地編寫復(fù)雜的并發(fā)邏輯,這種非常適合需要將多個接口結(jié)果串起來的流程,有點像langChain,我們直接看示例代碼:
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 第一個異步任務(wù)
return "Hello";
}).thenApplyAsync(result -> {
// 第二個異步任務(wù),依賴第一個任務(wù)的結(jié)果
return result + " World";
}).thenApplyAsync(result -> {
// 第三個異步任務(wù),依賴第二個任務(wù)的結(jié)果
return result + "!";
});
try {
System.out.println(future.get());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}這個示例比較簡單,在實際使用中,我們可以將三個任務(wù)替換成任何需要相互依賴的任務(wù),你只需要定義每個任務(wù)的輸入和輸出,然后將它們連接在一起,就可以創(chuàng)建一個復(fù)雜的并發(fā)流程。
結(jié)果組合
CompletableFuture提供了幾個方法來組合或合并多個CompletableFuture的結(jié)果。
最常用的可能是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方法將它們的結(jié)果組合在一起。
以下是allOf方法的使用示例:
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
return "Hello";
});
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
return "World!";
});
CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2);
// 當(dāng)所有的 CompletableFuture 都完成時,獲取結(jié)果
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)了類似的功能,但兩者的側(cè)重點不同,thenCombine實現(xiàn)的是對執(zhí)行結(jié)果的聚合,而allOf實現(xiàn)的是對執(zhí)行完狀態(tài)的聚合,還是需要你去顯式調(diào)用get()方法去獲取結(jié)果的,所以你還是得根據(jù)具體需求來選擇使用thenCombine還是allOf。
回調(diào)
使用Future時,我們只能通過get()或者isDone()方法來獲取到任務(wù)是否執(zhí)行完成了,無法讓任務(wù)執(zhí)行完成后主動通知到我們,但CompletableFuture提供了一些回調(diào)方法,可以讓任務(wù)執(zhí)行完成后執(zhí)行結(jié)果處理的任務(wù)或者執(zhí)行一些通知的邏輯。
CompletableFuture提供了幾個方法來設(shè)置回調(diào),如thenApply, thenAccept, 和 thenRun。
具體看代碼示例:
public static void main(String[] args) {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// 異步任務(wù)
return "Hello, World!";
});
future.thenApply(result -> {
// 當(dāng) future 完成時,這個回調(diào)會被調(diào)用。
// result 參數(shù)是 future 的結(jié)果。
// 這個回調(diào)的結(jié)果會被包裝成一個新的 CompletableFuture。
return result.length();
}).thenAccept(length -> {
// 當(dāng)前面的 CompletableFuture 完成時,這個回調(diào)會被調(diào)用。
// length 參數(shù)是前面 CompletableFuture 的結(jié)果。
// 這個回調(diào)沒有返回值。
System.out.println(length); // 輸出 "Hello, World!" 的長度
}).thenRun(() -> {
// 當(dāng)前面的 CompletableFuture 完成時,這個回調(diào)會被調(diào)用。
// 這個回調(diào)沒有輸入?yún)?shù),也沒有返回值。
System.out.println("Done");
});
}thenApply、thenAccept和thenRun都是在任務(wù)執(zhí)行完成后被調(diào)起,但他們定位有所不同,thenApply處理的結(jié)果可以被包裝成一個新的CompletableFuture,只后可以繼續(xù)鏈?zhǔn)秸{(diào)用。
但thenAccept只能接受輸入,無法提供輸出,所以他的定位是任務(wù)的收尾,比如可以將結(jié)果輸出。
而thenRun既沒有輸入也沒有輸出,所以它只是獲取到了任務(wù)執(zhí)行完的狀態(tài),任務(wù)的執(zhí)行結(jié)果是獲取不到的,所以它是最弱的。
總結(jié)
總結(jié)下本文,Future 盡管有用,但在功能上還是相對簡單。
CompletableFuture不僅提供了獲取執(zhí)行結(jié)果的能力,它還增添了異常處理、手動完成、鏈?zhǔn)讲僮?、結(jié)果組合和回調(diào)等強大功能。
- 異常處理: 利用
exceptionally和handle方法,可以簡化異常處理流程,使其更加直觀和易于管理。 - 手動設(shè)置結(jié)果:
CompletableFuture允許我們在任意時間點手動設(shè)置結(jié)果,增加了靈活性。 - 鏈?zhǔn)讲僮?/strong>: 通過鏈?zhǔn)讲僮?,我們可以將不同的異步操作以流水線的方式串聯(lián)起來,編寫復(fù)雜的并發(fā)邏輯變得更加簡單。
- 結(jié)果組合:
thenCombine和allOf方法能夠?qū)⒍鄠€CompletableFuture的結(jié)果合并,為我們提供了更多處理異步操作結(jié)果的方式。 - 回調(diào): 提供了
thenApply,thenAccept, 和thenRun等方法,允許我們在任務(wù)完成后進行結(jié)果處理或其他邏輯操作。
通過這些特性,CompletableFuture顯著地提升了異步編程的能力和靈活性。無論是簡化異常處理,還是實現(xiàn)復(fù)雜的異步邏輯,CompletableFuture都能夠幫助我們寫出更清晰、更高效的代碼。我相信通過本文的示例代碼,你可以更深入地理解CompletableFuture的強大之處,并在你的項目中充分利用它來提升性能。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
SpringBoot?2.5.5整合輕量級的分布式日志標(biāo)記追蹤神器TLog的詳細過程
分布式追蹤系統(tǒng)是一個最終的解決方案,如果您的公司已經(jīng)上了分布式追蹤系統(tǒng),這篇文章主要介紹了SpringBoot?2.5.5整合輕量級的分布式日志標(biāo)記追蹤神器TLog,需要的朋友可以參考下2022-10-10
使用Spring boot 的profile功能實現(xiàn)多環(huán)境配置自動切換
這篇文章主要介紹了使用Spring boot 的profile功能實現(xiàn)多環(huán)境配置自動切換的相關(guān)知識,非常不錯,具有一定的參考借鑒價值 ,需要的朋友可以參考下2018-11-11
基于servlet實現(xiàn)統(tǒng)計網(wǎng)頁訪問次數(shù)
這篇文章主要為大家詳細介紹了基于servlet實現(xiàn)統(tǒng)計網(wǎng)頁訪問次數(shù),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02
基于SpringBoot+Redis實現(xiàn)一個簡單的限流器
在Spring?Boot中使用Redis和過濾器實現(xiàn)請求限流,過濾器將在每個請求到達時檢查請求頻率,并根據(jù)設(shè)定的閾值進行限制,這樣可以保護您的應(yīng)用程序免受惡意請求或高并發(fā)請求的影響,本文我們通過Spring?Boot?+Redis?實現(xiàn)一個輕量級的消息隊列,需要的朋友可以參考下2023-08-08
Java Fluent Mybatis 聚合查詢與apply方法詳解流程篇
Java中常用的ORM框架主要是mybatis, hibernate, JPA等框架。國內(nèi)又以Mybatis用的多,基于mybatis上的增強框架,又有mybatis plus和TK mybatis等。今天我們介紹一個新的mybatis增強框架 fluent mybatis關(guān)于聚合查詢、apply方法詳解2021-10-10

