实践:使用了CompletableFuture之后,程序性能提升了三倍
CompletableFuture
相比于jdk5所提出的future概念,future在执行的时候支持异步处理,但是在回调的过程中依旧是难免会遇到需要等待的情况。
在jdk8里面,出现了CompletableFuture的新概念,支持对于异步处理完成任务之后自行处理数据。当发生异常的时候也能按照自定义的逻辑来处理。
如何通过使用CompletableFuture提升查询的性能呢?
下边我举个例子来演示:
首先我们定义一个UserInfo对象:
/**
* @author idea
* @data 2020/2/22
*/
public class UserInfo {
private Integer id;
private String name;
private Integer jobId;
private String jobDes;
private Integer carId;
private String carDes;
private Integer homeId;
private String homeDes;
public Integer getId() {
return id;
}
public UserInfo setId(Integer id) {
this.id = id;
return this;
}
public String getName() {
return name;
}
public UserInfo setName(String name) {
this.name = name;
return this;
}
public Integer getJobId() {
return jobId;
}
public UserInfo setJobId(Integer jobId) {
this.jobId = jobId;
return this;
}
public String getJobDes() {
return jobDes;
}
public UserInfo setJobDes(String jobDes) {
this.jobDes = jobDes;
return this;
}
public Integer getCarId() {
return carId;
}
public UserInfo setCarId(Integer carId) {
this.carId = carId;
return this;
}
public String getCarDes() {
return carDes;
}
public UserInfo setCarDes(String carDes) {
this.carDes = carDes;
return this;
}
public Integer getHomeId() {
return homeId;
}
public UserInfo setHomeId(Integer homeId) {
this.homeId = homeId;
return this;
}
public String getHomeDes() {
return homeDes;
}
public UserInfo setHomeDes(String homeDes) {
this.homeDes = homeDes;
return this;
}
}
这个对象里面的homeid,jobid,carid都是用于匹配对应的住房信息描述,职业信息描述,购车信息描述。
对于将id转换为描述信息的方式需要通过额外的sql查询,这里做了个简单的工具类来进行模拟:
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
* @author idea
* @data 2020/2/22
*/
public class QueryUtils {
public String queryCar(Integer carId){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "car_desc";
}
public String queryJob(Integer jobId){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "job_desc";
}
public String queryHome(Integer homeId){
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "home_desc";
}
}
这个工具类的功能看起来会比较通俗易懂,在常规的逻辑里面,我们做批量对象的转换大多数都是基于List遍历,然后在循环里面批量查询,这样的方式并非说不行,而是显得比较过于“暴力”。
假设每次查询需要消耗1s,那么遍历的一个size为n的集合查询消耗的时间就是n * 3s。
下边来介绍一种更为方便的技巧:CompletableFuture
定义一个QuerySupplier 实现Supplier接口,根据注入的类型进行转译查询:
import java.util.function.Supplier;
public class QuerySuppiler implements Supplier<String> {
private Integer id;
private String type;
private QueryUtils queryUtils;
public QuerySuppiler(Integer id, String type,QueryUtils queryUtils) {
this.id = id;
this.type = type;
this.queryUtils=queryUtils;
}
@Override
public String get() {
if("home".equals(type)){
return queryUtils.queryHome(id);
}else if ("job".equals(type)){
return queryUtils.queryJob(id);
}else if ("car".equals(type)){
return queryUtils.queryCar(id);
}
return null;
}
}
由于对应的carid,homeid,jobid都需要到指定的k,v配置表里面通过核心查询包装器来进行转译,因此通常的做法就是在for循环里面一个个地进行遍历解析,这样的做法也比较易于理解。
QuerySuppiler 是我写的一个用于做对象解析的服务,代码如下所示:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
* @author idea
* @data 2020/2/22
*/
public class QueryUserService {
private Supplier<QueryUtils> queryUtilsSupplier = QueryUtils::new;
public UserInfo converUserInfo(UserInfo userInfo) {
QuerySuppiler querySuppiler1 = new QuerySuppiler(userInfo.getCarId(), "car", queryUtilsSupplier.get());
CompletableFuture<String> getCarDesc = CompletableFuture.supplyAsync(querySuppiler1);
getCarDesc.thenAccept(new Consumer<String>() { --1
@Override
public void accept(String carDesc) {
userInfo.setCarDes(carDesc);
}
});
QuerySuppiler querySuppiler2 = new QuerySuppiler(userInfo.getHomeId(), "home", queryUtilsSupplier.get());
CompletableFuture<String> getHomeDesc = CompletableFuture.supplyAsync(querySuppiler2);
getHomeDesc.thenAccept(new Consumer<String>() { --2
@Override
public void accept(String homeDesc) {
userInfo.setHomeDes(homeDesc);
}
});
QuerySuppiler querySuppiler3 = new QuerySuppiler(userInfo.getJobId(), "job", queryUtilsSupplier.get());
CompletableFuture<String> getJobDesc = CompletableFuture.supplyAsync(querySuppiler3);
getJobDesc.thenAccept(new Consumer<String>() { --3
@Override
public void accept(String jobDesc) {
userInfo.setJobDes(jobDesc);
}
});
CompletableFuture<Void> getUserInfo = CompletableFuture.allOf(getCarDesc, getHomeDesc, getJobDesc);
getUserInfo.thenAccept(new Consumer<Void>() {
@Override
public void accept(Void result) {
System.out.println("全部完成查询" );
}
});
getUserInfo.join(); --4
return userInfo;
}
public static void main(String[] args) {
long begin= System.currentTimeMillis();
//多线程环境需要注意线程安全问题
List<UserInfo> userInfoList=Collections.synchronizedList(new ArrayList<>());
for(int i=0;i<=20;i++){
UserInfo userInfo=new UserInfo();
userInfo.setId(i);
userInfo.setName("username"+i);
userInfo.setCarId(i);
userInfo.setJobId(i);
userInfo.setHomeId(i);
userInfoList.add(userInfo);
}
//stream 查询一个用户花费3s 并行计算后一个用户1秒左右 查询21个用户花费21秒
//parallelStream 速度更慢
userInfoList.stream()
.map(userInfo->{
QueryUserService queryUserService=new QueryUserService();
userInfo =queryUserService.converUserInfo(userInfo);
return userInfo;
}).collect(Collectors.toList());
System.out.println("=============");
long end=System.currentTimeMillis();
System.out.println(end-begin);
}
}
看看这段代码的—1,—2,—3部分,三个执行点的位置在使用了thenAccept组装数据之后,还是可以避开串行化获取数据的情况。只有在—4的位置才会发生堵塞。这样对于性能的提升效果更佳。
这里进行模拟测试,采用原始暴力手段查询所消耗的时间是20 * 3 =60秒,但是这里使用了CompletableFuture之后,查询的时间就会缩短为了21秒。
结果:
全部完成查询
=============
21223
这是一种使用了空间换时间的思路,或许你会说,异步查询如果使用FutureTask是不是也可以呢。嗯嗯,是的,但是使用future有个问题,就是在于返回获取异步结果的时候需要有等待状态,这个等待的状态是需要消耗时间进行堵塞的。
这里我也做了关于使用普通FutureTask来执行查询优化的结果:
/**
* 使用 FutureTask 来优化查询
*
* @param userInfo
* @return
*/
public UserInfo converUserInfoV2(UserInfo userInfo) {
Callable<String> homeCallable=new Callable() {
@Override
public Object call() throws Exception {
return queryUtilsSupplier.get().queryHome(userInfo.getHomeId());
}
};
FutureTask<String> getHomeDesc=new FutureTask<>(homeCallable);
new Thread(getHomeDesc).start();
futureMap.put("homeCallable",getHomeDesc);
Callable<String> carCallable=new Callable() {
@Override
public Object call() throws Exception {
return queryUtilsSupplier.get().queryCar(userInfo.getCarId());
}
};
FutureTask<String> getCarDesc=new FutureTask(carCallable);
new Thread(getCarDesc).start();
futureMap.put("carCallable",getCarDesc);
Callable<String> jobCallable=new Callable() {
@Override
public Object call() throws Exception {
return queryUtilsSupplier.get().queryCar(userInfo.getJobId());
}
};
FutureTask<String> getJobDesc=new FutureTask<>(jobCallable);
new Thread(getJobDesc).start();
futureMap.put("jobCallable",getJobDesc);
try {
userInfo.setHomeDes((String) futureMap.get("homeCallable").get());
userInfo.setCarDes((String)futureMap.get("carCallable").get());
userInfo.setJobDes((String)futureMap.get("jobCallable").get());
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("该对象完成查询" );
return userInfo;
}
经过测试,使用 futuretask 进行优化的查询结果只有47s左右,远远不及CompletableFuture的性能高效.这是因为使用了futuretask的get方法依然是存在堵塞的情况。
关键部分看这段内容:
userInfo.setHomeDes((String) futureMap.get("homeCallable").get()); --1
userInfo.setCarDes((String)futureMap.get("carCallable").get()); --2
userInfo.setJobDes((String)futureMap.get("jobCallable").get()); --3
—1代码在执行的时候遇到了堵塞,然后—2和—3的get也需要进行等待,因此使用常规的futuretask进行优化,这里难免还是会有堵塞的情况。
实践:使用了CompletableFuture之后,程序性能提升了三倍的更多相关文章
- C# 程序性能提升篇-1、装箱和拆箱,枚举的ToString浅析
前景提要: 编写程序时,也许你不经意间,就不知不觉的使程序代码,发生了装箱和拆箱,从而降低了效率,不要说就发生那么一次两次,如果说是程序中发生了循环.网络程序(不断请求处理的)等这些时候,减少装箱和拆 ...
- C# 程序性能提升篇-2、类型(字段类型、class和struct)的错误定义所影响性能浅析
前景提要: 编写程序时,也许你不经意间,就不知不觉的定义了错误的类型,从而发生了额外的性能消耗,从而降低了效率,不要说就发生那么一次两次,如果说是程序中发生了循环.网络程序(不断请求处理的)等这些时候 ...
- 11个显著提升 ASP.NET 应用程序性能的技巧——第1部分
[编者按]本文出自站外作者 Brij Bhushan Mishra ,Brij 是微软 MVP-ASP.NET/IIS.C# Corner MVP.CodeProject Insider,前 Code ...
- 增强iOS应用程序性能的提示和技巧(25个)
转自 http://www.cocoachina.com/newbie/basic/2013/0522/6259.html 在开发iOS应用程序时,让程序具有良好的性能是非常关键的.这也是用户所期望的 ...
- (转)25个增强iOS应用程序性能的提示和技巧--高级篇
高级当且仅当下面这些技巧能够解决问题的时候,才使用它们: 22.加速启动时间23.使用Autorelease Pool24.缓存图片 — 或者不缓存25.尽量避免Date格式化 高级性能提升 寻找一些 ...
- VNF网络性能提升解决方案及实践
VNF网络性能提升解决方案及实践 2016年7月 作者: 王智民 贡献者: 创建时间: 2016-7-20 稳定程度: 初稿 修改历史 版本 日期 修订人 说明 1.0 20 ...
- [.net 面向对象程序设计进阶] (15) 缓存(Cache)(二) 利用缓存提升程序性能
[.net 面向对象程序设计进阶] (15) 缓存(Cache)(二) 利用缓存提升程序性能 本节导读: 上节说了缓存是以空间来换取时间的技术,介绍了客户端缓存和两种常用服务器缓布,本节主要介绍一种. ...
- 智能SQL优化工具--SQL Optimizer for SQL Server(帮助提升数据库应用程序性能,最大程度地自动优化你的SQL语句 )
SQL Optimizer for SQL Server 帮助提升数据库应用程序性能,最大程度地自动优化你的SQL语句 SQL Optimizer for SQL Server 让 SQL Serve ...
- TOP100summit:【分享实录-华为】微服务场景下的性能提升最佳实践
本篇文章内容来自2016年TOP100summit华为架构部资深架构师王启军的案例分享.编辑:Cynthia 王启军:华为架构部资深架构师.负责华为的云化.微服务架构推进落地,前后参与了华为手机祥云4 ...
随机推荐
- SQLiteOpenHelperde的oncreate方法
main中
- [bzoj4571] [loj#2016] [Scoi2016] 美味
Description 一家餐厅有 \(n\) 道菜,编号 \(1\)...\(n\) ,大家对第 \(i\) 道菜的评价值为 \(ai\)( \(1 \leq i \leq n\) ).有 \(m\ ...
- applyColorMap 在OpenCV中对灰度图进行颜色映射,实现数据的色彩化
什么是色彩映射: 说直白点就是将各种数据映射成颜色信息,例如:温度,高度,压力,密度,湿度,城市拥堵数据等等 色彩化后更加直观表达 在OpenCV里可以使用 Mat im_gray = imread( ...
- composer intall 报错
报错 [Composer\Exception\NoSslException] The openssl extension is required for SSL/TLS protection but ...
- Leetcode 题目整理-8 Count and Say
38. Count and Say The count-and-say sequence is the sequence of integers beginning as follows: 1, 11 ...
- springcloud 依赖版本问题
SpringCloud 版本: 版本名称 版本 Finchley snapshot版 Edgware snapshot版 Dalston SR1 当前最新稳定版本 Camden SR7 稳定版本 Br ...
- 解决docker容器无网络、无法连接互联网----长期更新
众所周知,docker有三种默认的网络模式,分别是: bridge host none 然后需要注意的就是内核转发这个,一定不要忘了!配置方法如下 echo -e "net.ipv4.ip_ ...
- pyinstaller 还原python代码的方法
pyinstaller 的作用就是将python打包成对应平台的可执行文件.一般这种可执行文件的体积都比较大. 我们可以先通过逆向软件查看一下具体信息 查看字符串信息 只要有诸如以上的字符串 就说明这 ...
- 如何快速打好Java基础?
二哥,我是一名大学生,专业是电力工程,但想自学 Java,如何快速打好基础呢? 微信上 tison 向我提出了这个问题.我想我是有资格来回答的,从北京奥运会那年开始学 Java,到现在已经有 10 多 ...
- python day02练习和作业
# 1.请用代码实现:利用下划线将列表的每一个元素拼接成字符串,li=[‘alex’, ‘eric’, ‘rain’]# li=['alex','eric','rain']# print('_'.jo ...