詳解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)用到實(shí)際工作當(dāng)中。CompletableFuture內(nèi)部的用法還有許多,但個人用到的場景大多都是并行處理,對其他場景感興趣的小伙伴可以另行百度搜索。
場景說明:
寫一個接口,調(diào)用另外兩個HTTP接口,分別獲取二十四節(jié)氣和星座,最后放在一起返回。
用法
1、在線API
我們訪問極速數(shù)據(jù)網(wǎng)站,注冊一個賬號,就可以免費(fèi)使用里面的一些在線API,平均每天有100次免費(fèi)機(jī)會,對于我這樣經(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、效果
可以看到,兩個接口一共耗費(fèi)了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)部處理了異常無需手動處理,點(diǎn)進(jìn)去一看便知。
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ù)需要遠(yuǎn)程調(diào)用5個第三方laji廠家的接口,每個接口假設(shè)都耗時5秒,使用CompletableFuture并行處理最終需要多久?
答案是肯定的,同步查詢需要25秒左右,CompletableFuture并行處理還是5秒左右,也就是說,同一個接口中,調(diào)用的耗時接口越多,CompletableFuture優(yōu)化的幅度就越大。
示例代碼
可以下載我的完整示例代碼本地按需測試,里面有我的極速數(shù)據(jù)API的key,省得自己注冊賬號了,每天免費(fèi)就100次,先到先得哦。
到此這篇關(guān)于詳解Java8 CompletableFuture的并行處理用法的文章就介紹到這了,更多相關(guān)Java8 CompletableFuture內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java8?CompletableFuture?runAsync學(xué)習(xí)總結(jié)submit()?execute()等
- Java?CompletableFuture實(shí)現(xiàn)多線程異步編排
- Java8 使用工廠方法supplyAsync創(chuàng)建CompletableFuture實(shí)例
- Java8 自定義CompletableFuture的原理解析
- Java8 CompletableFuture 異步執(zhí)行操作
- Java并發(fā) CompletableFuture異步編程的實(shí)現(xiàn)
- Java8新的異步編程方式CompletableFuture實(shí)現(xiàn)
- Java8 CompletableFuture詳解
- Java中的CompletableFuture原理與用法
相關(guān)文章
Spring boot實(shí)現(xiàn)應(yīng)用打包部署的示例
本篇文章主要介紹了Spring boot實(shí)現(xiàn)應(yīng)用打包部署的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-11-11
詳解Java8新特性Stream之list轉(zhuǎn)map及問題解決
這篇文章主要介紹了詳解Java8新特性Stream之list轉(zhuǎn)map及問題解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
解決Eclipse配置Tomcat出現(xiàn)Cannot create a server using the selected
這篇文章主要介紹了解決Eclipse配置Tomcat出現(xiàn)Cannot create a server using the selected type錯誤的相關(guān)資料,需要的朋友可以參考下2017-02-02
SpringBoot3.X配置OAuth的代碼實(shí)踐
在進(jìn)行Java后端技術(shù)框架版本升級時,特別是將SpringBoot從2.X升級到3.X,發(fā)現(xiàn)對OAuth的配置有大幅變更,新版本中刪除了多個常用配置類,本文給大家介紹SpringBoot3.X配置OAuth的相關(guān)知識,感興趣的朋友一起看看吧2024-09-09
springboot2.0如何通過fastdfs實(shí)現(xiàn)文件分布式上傳
這篇文章主要介紹了springboot2.0如何通過fastdfs實(shí)現(xiàn)文件分布式上傳,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-12-12
Java程序運(yùn)行之JDK,指令javac java解讀
這篇文章主要介紹了Java程序運(yùn)行之JDK,指令javac java,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01

