详解Java8 CompletableFuture的并行处理用法
作者:Java分享客栈 发布时间:2023-07-27 11:48:06
前言
工作中你可能会遇到很多这样的场景,一个接口,要从其他几个service调用查询方法,分别获取到需要的值之后再封装数据返回。
还可能在微服务中遇到类似的情况,某个服务的接口,要使用好几次feign去调用其他服务的方法获取数据,最后拿到想要的值并封装返回给前端。
这样的场景下,当某个或多个rpc调用的方法比较耗时,整个接口的响应就会非常慢。Java8之后,有一个工具非常适合处理这种场景,就是CompletableFuture。
场景
本章主要讲解CompletableFuture的并行处理用法,来针对这种很常见的场景,帮助大家快速掌握并应用到实际工作当中。CompletableFuture内部的用法还有许多,但个人用到的场景大多都是并行处理,对其他场景感兴趣的小伙伴可以另行百度搜索。
场景说明:
写一个接口,调用另外两个HTTP接口,分别获取二十四节气和星座,最后放在一起返回。
用法
1、在线API
我们访问极速数据网站,注册一个账号,就可以免费使用里面的一些在线API,平均每天有100次免费机会,对于我这样经常本地做一些测试的人来说完全够用了。
这里,我使用了其中的查询二十四节气API,和查询星座API,后面会提供案例代码,也可以直接使用我的。
2、编写在线API查询
这里,我们在查询时,模拟耗时的情况。
1.查询二十四节气
package com.example.async.service;
import cn.hutool.http.HttpUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* <p>
* 查询二十四节气的服务
* </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("[二十四节气]>>>> 异常: {}", 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>
* 查询星座的服务
* </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、编写查询服务
package com.example.async.service;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* <p>
* 查询服务
* </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;
}
/**
* 同步返回结果
* @return 结果
*/
public Map<String, Object> query() {
// 1、查询二十四节气
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 结果
*/
@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并行查询
现在我们来使用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>
* 查询服务
* </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;
}
/**
* 异步返回结果
* @return 结果
*/
public Map<String, Object> queryAsync() {
Map<String, Object> map = new HashMap<>();
// 1、查询二十四节气
CompletableFuture<String> twentyFourQuery = CompletableFuture.supplyAsync(twentyFourService::getResult);
twentyFourQuery.thenAccept((result) -> {
log.info("查询二十四节气结果:{}", result);
map.put("twentyFourResult", result);
}).exceptionally((e) -> {
log.error("查询二十四节气异常: {}", e.getMessage(), e);
map.put("twentyFourResult", "");
return null;
});
// 2、查询星座
CompletableFuture<String> constellationQuery = CompletableFuture.supplyAsync(constellationService::getResult);
constellationQuery.thenAccept((result) -> {
log.info("查询星座结果:{}", 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()-内部抛出了异常需手动处理; join()-内部处理了异常无需手动处理,点进去一看便知。
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 结果
*/
@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效果
可以看到,时间缩短了一倍。
思考
如果在微服务中,有一个很复杂的业务需要远程调用5个第三方laji厂家的接口,每个接口假设都耗时5秒,使用CompletableFuture并行处理最终需要多久?
答案是肯定的,同步查询需要25秒左右,CompletableFuture并行处理还是5秒左右,也就是说,同一个接口中,调用的耗时接口越多,CompletableFuture优化的幅度就越大。
示例代码
可以下载我的完整示例代码本地按需测试,里面有我的极速数据API的key,省得自己注册账号了,每天免费就100次,先到先得哦。
来源:https://mp.weixin.qq.com/s/E3Sw8rm9LU-yggANh5FOBg


猜你喜欢
- 最近由于项目开发使用到了动态布局,因为打包sdk ,sdk 这块activity 需要一些layout 文件 。而做过sdk 开发的小伙伴应
- 首先让大家有个全局的认识,直接上个项目,看看仅仅通过这几行代码,竟然就能完成如此强悍的功能。下篇再仔细讲讲为什么要这么写。效果图:实现了三个
- 开发环境: IDEA 2022.1.41. 概述虽然webservice这块使用很少,但在局域网作服务还是相当不错。今天突生想法,想做一个来
- 接收JSON浏览器传来的参数,可以是 key/value 形式的,也可以是一个 JSON 字符串。在 Jsp/Servlet 中,我们接收
- 本文实例讲述了C#使用Matrix执行缩放的方法。分享给大家供大家参考。具体实现方法如下:using System;using System
- 看过阿里巴巴开发手册的同学应该都会对Integer临界值127有点印象。原文中写的是:【强制】所有整型包装类对象之间值的比较,全部使用 eq
- 本文实例为大家分享了Android实现聊天界面的具体代码,供大家参考,具体内容如下文件目录在app下的build.gradle中添加依赖库(
- 本文实例讲述了Android编程之OpenGL绘图技巧。分享给大家供大家参考,具体如下:很久不用OpenGL ES绘图,怕自己忘记了,于是重
- 一、大致界面介绍:图1图2图3图4图1:手势密码绘制界面 【主要是绘制上方的9个提示图标和9个宫格密码图标】图2:设置手势密码 【监听手势的
- 一、目的针对不同地区,设置不同的语言信息。SpringBoot国际化配置文件默认放在classpath:message.properties
- 前言虽然Aandroid目前已经有RecyclerView了、非常强大的一个View、可以直接控制成ListView以及GridView等、
- C#客户端程序,生成后是一个exe,如果带有大量的dll,那么dll和exe会混乱在一起,看起来非常混乱,我们可以建立一个文件夹,把dll放
- 在很多场景下,maven不能直接访问到外网时,使用代理是其中常见的一种方式。这篇文章整理一下常见的maven中设置代理的方法。代理服务器代理
- @Aspect中有5种通知@Before:前置通知, 在方法执行之前执行@Aroud:环绕通知, 围绕着方法执行@After:后置通知, 在
- 本文实例讲述了Android使用ViewFlipper和GestrueDetector共同实现滑屏效果。分享给大家供大家参考,具体如下:关于
- 1 简介Springboot是最简单的使用Spring的方式,而MongoDB是最流行的NoSQL数据库。两者在分布式、微服务架构中使用率极
- 分栏是报刊、书籍、杂志常用的排版样式,它不仅能方便阅读,同时也能增加页面的美观度。本文将介绍如何在Java应用程序中给Word文档添加多个栏
- 数据类型大小范围默认值byte(字节)8-128 - 1270shot(短整型)16-32768 - 327680int(整型)32-214
- 最近做一个C#项目,需要对radis进行读写。首先引入System.Configuration,如下实现代码如下:public class
- 问题背景: 我要在一个表单里同时一次性提交多名乘客的个人信息到SpringMVC,前端HTML和SpringMVC Controller里该