詳解Java8?CompletableFuture的并行處理用法
前言
工作中你可能會遇到很多這樣的場景,一個接口,要從其他幾個service調(diào)用查詢方法,分別獲取到需要的值之后再封裝數(shù)據(jù)返回。
還可能在微服務(wù)中遇到類似的情況,某個服務(wù)的接口,要使用好幾次feign去調(diào)用其他服務(wù)的方法獲取數(shù)據(jù),最后拿到想要的值并封裝返回給前端。
這樣的場景下,當(dāng)某個或多個rpc調(diào)用的方法比較耗時,整個接口的響應(yīng)就會非常慢。Java8之后,有一個工具非常適合處理這種場景,就是CompletableFuture。
場景
本章主要講解CompletableFuture的并行處理用法,來針對這種很常見的場景,幫助大家快速掌握并應(yīng)用到實際工作當(dāng)中。CompletableFuture內(nèi)部的用法還有許多,但個人用到的場景大多都是并行處理,對其他場景感興趣的小伙伴可以另行百度搜索。
場景說明:
寫一個接口,調(diào)用另外兩個HTTP接口,分別獲取二十四節(jié)氣和星座,最后放在一起返回。
用法
1、在線API
我們訪問極速數(shù)據(jù)網(wǎng)站,注冊一個賬號,就可以免費使用里面的一些在線API,平均每天有100次免費機會,對于我這樣經(jīng)常本地做一些測試的人來說完全夠用了。
這里,我使用了其中的查詢二十四節(jié)氣API,和查詢星座API,后面會提供案例代碼,也可以直接使用我的。
2、編寫在線API查詢
這里,我們在查詢時,模擬耗時的情況。
1.查詢二十四節(jié)氣
package com.example.async.service; import cn.hutool.http.HttpUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; /** * <p> * 查詢二十四節(jié)氣的服務(wù) * </p> * * @author 福隆苑居士,公眾號:【Java分享客?!? * @since 2022-04-26 15:25 */ @Service @Slf4j public class TwentyFourService { public static final String APPKEY = "xxxxxx";// 你的appkey public static final String URL = "https://api.jisuapi.com/jieqi/query"; public String getResult() { String url = URL + "?appkey=" + APPKEY; String result = HttpUtil.get(url); // 模擬耗時 try { TimeUnit.SECONDS.sleep(5); } catch (Exception e) { log.error("[二十四節(jié)氣]>>>> 異常: {}", e.getMessage(), e); } return result; } }
2.查詢星座
package com.example.async.service; import cn.hutool.http.HttpUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; /** * <p> * 查詢星座的服務(wù) * </p> * * @author 福隆苑居士,公眾號:【Java分享客棧】 * @since 2022-04-26 15:25 */ @Service @Slf4j public class ConstellationService { public static final String APPKEY = "xxxxxx";// 你的appkey public static final String URL = "https://api.jisuapi.com/astro/all"; public String getResult() { String url = URL + "?appkey=" + APPKEY; String result = HttpUtil.get(url); // 模擬耗時 try { TimeUnit.SECONDS.sleep(5); } catch (Exception e) { log.error("[星座]>>>> 異常: {}", e.getMessage(), e); } return result; } }
3、編寫查詢服務(wù)
package com.example.async.service; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; /** * <p> * 查詢服務(wù) * </p> * * @author 福隆苑居士,公眾號:【Java分享客?!? * @since 2022-04-26 17:38 */ @Service @Slf4j public class QueryService { private final TwentyFourService twentyFourService; private final ConstellationService constellationService; public QueryService(TwentyFourService twentyFourService, ConstellationService constellationService) { this.twentyFourService = twentyFourService; this.constellationService = constellationService; } /** * 同步返回結(jié)果 * @return 結(jié)果 */ public Map<String, Object> query() { // 1、查詢二十四節(jié)氣 String twentyFourResult = twentyFourService.getResult(); // 2、查詢星座 String constellationResult = constellationService.getResult(); // 3、返回 Map<String, Object> map = new HashMap<>(); map.put("twentyFourResult", twentyFourResult); map.put("constellationResult", constellationResult); return map; } }
4、編寫測試接口
這里,我們專門加上了耗時計算。
package com.example.async.controller; import cn.hutool.core.date.TimeInterval; import com.example.async.service.QueryService; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Map; /** * <p> * 測試 * </p> * * @author 福隆苑居士,公眾號:【Java分享客棧】 * @since 2022-04-26 17:35 */ @RestController @RequestMapping("/api") @Slf4j public class TestController { private final QueryService queryService; public TestController(QueryService queryService) { this.queryService = queryService; } /** * 同步查詢 * @return 結(jié)果 */ @GetMapping("/query") public ResponseEntity<Map<String, Object>> query() { // 計時 final TimeInterval timer = new TimeInterval(); timer.start(); Map<String, Object> map = queryService.query(); map.put("costTime", timer.intervalMs() + " ms"); return ResponseEntity.ok().body(map); } }
5、效果
可以看到,兩個接口一共耗費了10秒左右才返回。
6、CompletableFuture并行查詢
現(xiàn)在我們來使用CompletableFuture改造下接口,并行查詢兩個HTTP接口再返回。
package com.example.async.service; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; /** * <p> * 查詢服務(wù) * </p> * * @author 福隆苑居士,公眾號:【Java分享客?!? * @since 2022-04-26 17:38 */ @Service @Slf4j public class QueryService { private final TwentyFourService twentyFourService; private final ConstellationService constellationService; public QueryService(TwentyFourService twentyFourService, ConstellationService constellationService) { this.twentyFourService = twentyFourService; this.constellationService = constellationService; } /** * 異步返回結(jié)果 * @return 結(jié)果 */ public Map<String, Object> queryAsync() { Map<String, Object> map = new HashMap<>(); // 1、查詢二十四節(jié)氣 CompletableFuture<String> twentyFourQuery = CompletableFuture.supplyAsync(twentyFourService::getResult); twentyFourQuery.thenAccept((result) -> { log.info("查詢二十四節(jié)氣結(jié)果:{}", result); map.put("twentyFourResult", result); }).exceptionally((e) -> { log.error("查詢二十四節(jié)氣異常: {}", e.getMessage(), e); map.put("twentyFourResult", ""); return null; }); // 2、查詢星座 CompletableFuture<String> constellationQuery = CompletableFuture.supplyAsync(constellationService::getResult); constellationQuery.thenAccept((result) -> { log.info("查詢星座結(jié)果:{}", result); map.put("constellationResult", result); }).exceptionally((e) -> { log.error("查詢星座異常: {}", e.getMessage(), e); map.put("constellationResult", ""); return null; }); // 3、allOf-兩個查詢必須都完成 CompletableFuture<Void> allQuery = CompletableFuture.allOf(twentyFourQuery, constellationQuery); CompletableFuture<Map<String, Object>> future = allQuery.thenApply((result) -> { log.info("------------------ 全部查詢都完成 ------------------ "); return map; }).exceptionally((e) -> { log.error(e.getMessage(), e); return null; }); // 獲取異步方法返回值 // get()-內(nèi)部拋出了異常需手動處理; join()-內(nèi)部處理了異常無需手動處理,點進去一看便知。 future.join(); return map; } }
7、編寫測試接口
package com.example.async.controller; import cn.hutool.core.date.TimeInterval; import com.example.async.service.QueryService; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.Map; /** * <p> * 測試 * </p> * * @author 福隆苑居士,公眾號:【Java分享客棧】 * @since 2022-04-26 17:35 */ @RestController @RequestMapping("/api") @Slf4j public class TestController { private final QueryService queryService; public TestController(QueryService queryService) { this.queryService = queryService; } /** * 異步查詢 * @return 結(jié)果 */ @GetMapping("/queryAsync") public ResponseEntity<Map<String, Object>> queryAsync() { // 計時 final TimeInterval timer = new TimeInterval(); timer.start(); Map<String, Object> map = queryService.queryAsync(); map.put("costTime", timer.intervalMs() + " ms"); return ResponseEntity.ok().body(map); } }
8、CompletableFuture效果
可以看到,時間縮短了一倍。
思考
如果在微服務(wù)中,有一個很復(fù)雜的業(yè)務(wù)需要遠程調(diào)用5個第三方laji廠家的接口,每個接口假設(shè)都耗時5秒,使用CompletableFuture并行處理最終需要多久?
答案是肯定的,同步查詢需要25秒左右,CompletableFuture并行處理還是5秒左右,也就是說,同一個接口中,調(diào)用的耗時接口越多,CompletableFuture優(yōu)化的幅度就越大。
示例代碼
可以下載我的完整示例代碼本地按需測試,里面有我的極速數(shù)據(jù)API的key,省得自己注冊賬號了,每天免費就100次,先到先得哦。
到此這篇關(guān)于詳解Java8 CompletableFuture的并行處理用法的文章就介紹到這了,更多相關(guān)Java8 CompletableFuture內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java8?CompletableFuture?runAsync學(xué)習(xí)總結(jié)submit()?execute()等
- Java?CompletableFuture實現(xiàn)多線程異步編排
- Java8 使用工廠方法supplyAsync創(chuàng)建CompletableFuture實例
- Java8 自定義CompletableFuture的原理解析
- Java8 CompletableFuture 異步執(zhí)行操作
- Java并發(fā) CompletableFuture異步編程的實現(xiàn)
- Java8新的異步編程方式CompletableFuture實現(xiàn)
- Java8 CompletableFuture詳解
- Java中的CompletableFuture原理與用法
相關(guān)文章
IDEA快速搭建spring?boot項目教程(Spring?initializr)
這篇文章主要介紹了IDEA快速搭建spring?boot項目教程(Spring?initializr),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01Spring boot整合Mybatis實現(xiàn)級聯(lián)一對多CRUD操作的完整步驟
這篇文章主要給大家介紹了關(guān)于Spring boot整合Mybatis實現(xiàn)級聯(lián)一對多CRUD操作的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07Java數(shù)據(jù)結(jié)構(gòu)中七種排序算法實現(xiàn)詳解
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)中七種排序算法的實現(xiàn)方法,排序算法可分為兩大類,比較類排序和非比較類排序,顧名思義可知它們是通過比較來決定元素間的相對次序,需要詳細了解排序算法的朋友可以參考下2024-02-02SpringBoot Pom文件依賴及Starter啟動器詳細介紹
這篇文章主要介紹了SpringBoot Pom文件的依賴與starter啟動器的作用,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-09-09Java實現(xiàn)Excel導(dǎo)入導(dǎo)出數(shù)據(jù)庫的方法示例
這篇文章主要介紹了Java實現(xiàn)Excel導(dǎo)入導(dǎo)出數(shù)據(jù)庫的方法,結(jié)合實例形式分析了java針對Excel的讀寫及數(shù)據(jù)庫操作相關(guān)實現(xiàn)技巧,需要的朋友可以參考下2017-08-08Spring?boot?RedisTemplate?序列化服務(wù)化配置方式
這篇文章主要介紹了Springboot?RedisTemplate序列化服務(wù)化配置方式,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-07-07