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之后,程序性能提升了三倍的更多相关文章

  1. C# 程序性能提升篇-1、装箱和拆箱,枚举的ToString浅析

    前景提要: 编写程序时,也许你不经意间,就不知不觉的使程序代码,发生了装箱和拆箱,从而降低了效率,不要说就发生那么一次两次,如果说是程序中发生了循环.网络程序(不断请求处理的)等这些时候,减少装箱和拆 ...

  2. C# 程序性能提升篇-2、类型(字段类型、class和struct)的错误定义所影响性能浅析

    前景提要: 编写程序时,也许你不经意间,就不知不觉的定义了错误的类型,从而发生了额外的性能消耗,从而降低了效率,不要说就发生那么一次两次,如果说是程序中发生了循环.网络程序(不断请求处理的)等这些时候 ...

  3. 11个显著提升 ASP.NET 应用程序性能的技巧——第1部分

    [编者按]本文出自站外作者 Brij Bhushan Mishra ,Brij 是微软 MVP-ASP.NET/IIS.C# Corner MVP.CodeProject Insider,前 Code ...

  4. 增强iOS应用程序性能的提示和技巧(25个)

    转自 http://www.cocoachina.com/newbie/basic/2013/0522/6259.html 在开发iOS应用程序时,让程序具有良好的性能是非常关键的.这也是用户所期望的 ...

  5. (转)25个增强iOS应用程序性能的提示和技巧--高级篇

    高级当且仅当下面这些技巧能够解决问题的时候,才使用它们: 22.加速启动时间23.使用Autorelease Pool24.缓存图片 — 或者不缓存25.尽量避免Date格式化 高级性能提升 寻找一些 ...

  6. VNF网络性能提升解决方案及实践

    VNF网络性能提升解决方案及实践 2016年7月 作者:    王智民 贡献者:     创建时间:    2016-7-20 稳定程度:    初稿 修改历史 版本 日期 修订人 说明 1.0 20 ...

  7. [.net 面向对象程序设计进阶] (15) 缓存(Cache)(二) 利用缓存提升程序性能

    [.net 面向对象程序设计进阶] (15) 缓存(Cache)(二) 利用缓存提升程序性能 本节导读: 上节说了缓存是以空间来换取时间的技术,介绍了客户端缓存和两种常用服务器缓布,本节主要介绍一种. ...

  8. 智能SQL优化工具--SQL Optimizer for SQL Server(帮助提升数据库应用程序性能,最大程度地自动优化你的SQL语句 )

    SQL Optimizer for SQL Server 帮助提升数据库应用程序性能,最大程度地自动优化你的SQL语句 SQL Optimizer for SQL Server 让 SQL Serve ...

  9. TOP100summit:【分享实录-华为】微服务场景下的性能提升最佳实践

    本篇文章内容来自2016年TOP100summit华为架构部资深架构师王启军的案例分享.编辑:Cynthia 王启军:华为架构部资深架构师.负责华为的云化.微服务架构推进落地,前后参与了华为手机祥云4 ...

随机推荐

  1. SQLiteOpenHelperde的oncreate方法

    main中

  2. [bzoj4571] [loj#2016] [Scoi2016] 美味

    Description 一家餐厅有 \(n\) 道菜,编号 \(1\)...\(n\) ,大家对第 \(i\) 道菜的评价值为 \(ai\)( \(1 \leq i \leq n\) ).有 \(m\ ...

  3. applyColorMap 在OpenCV中对灰度图进行颜色映射,实现数据的色彩化

    什么是色彩映射: 说直白点就是将各种数据映射成颜色信息,例如:温度,高度,压力,密度,湿度,城市拥堵数据等等 色彩化后更加直观表达 在OpenCV里可以使用 Mat im_gray = imread( ...

  4. composer intall 报错

    报错 [Composer\Exception\NoSslException] The openssl extension is required for SSL/TLS protection but ...

  5. Leetcode 题目整理-8 Count and Say

    38. Count and Say The count-and-say sequence is the sequence of integers beginning as follows: 1, 11 ...

  6. springcloud 依赖版本问题

    SpringCloud 版本: 版本名称 版本 Finchley snapshot版 Edgware snapshot版 Dalston SR1 当前最新稳定版本 Camden SR7 稳定版本 Br ...

  7. 解决docker容器无网络、无法连接互联网----长期更新

    众所周知,docker有三种默认的网络模式,分别是: bridge host none 然后需要注意的就是内核转发这个,一定不要忘了!配置方法如下 echo -e "net.ipv4.ip_ ...

  8. pyinstaller 还原python代码的方法

    pyinstaller 的作用就是将python打包成对应平台的可执行文件.一般这种可执行文件的体积都比较大. 我们可以先通过逆向软件查看一下具体信息 查看字符串信息 只要有诸如以上的字符串 就说明这 ...

  9. 如何快速打好Java基础?

    二哥,我是一名大学生,专业是电力工程,但想自学 Java,如何快速打好基础呢? 微信上 tison 向我提出了这个问题.我想我是有资格来回答的,从北京奥运会那年开始学 Java,到现在已经有 10 多 ...

  10. python day02练习和作业

    # 1.请用代码实现:利用下划线将列表的每一个元素拼接成字符串,li=[‘alex’, ‘eric’, ‘rain’]# li=['alex','eric','rain']# print('_'.jo ...