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. 基于AOP和ThreadLocal实现的一个简单Http API日志记录模块

    Log4a 基于AOP和ThreadLocal实现的一个简单Http API日志记录模块 github地址 : https://github.com/EalenXie/log4a 在API每次被请求时 ...

  2. Django AJAX csrf

    1.原始 a.在HTML中添加 {% csrf_token %} b.在data中添加csrf_token对应input的 键值对 "csrfmiddlewaretoken" : ...

  3. CCPC-Wannafly Winter Camp Day 1

    B. 密码学 题意: 告诉你关于字符串加密的方法,然后给你一些加密操作和加密后的字符串,让你求原来的串 思路: 知道被加密后的串与加密字符可以向前推出被加密之前的串,不断向前模拟即可 #include ...

  4. 个人任务day4

    今日任务: 完成注册和登录界面的绘制,并创建用户数据库. 昨日成果: 完成博客数据库的录入和界面跳转.

  5. 网络io模型总结

    操作系统基本概念 首先来来说下操作系统,嗯,操作系统是计算机硬件的管理软件,是对计算机硬件的抽象,操作系统将应用程序分为用户态和内核态,例如驱动程序就位于内核态,而我们写的一般程序都是用户态,包括we ...

  6. 画布 canvas 的相关内容

    1.什么是canvas canvas也被叫做画布,是在JavaScript中完成网页图像制作的一个重要的途径,画布是一个矩形区域,在这个矩形区域中你可以利用好这里的每一个像素.同样在canvas中也有 ...

  7. mac如何用quick look预览多个文件或者图片

    1.先选中要查看的多个文件,然后点击 空格键 2.按住 command+return 就可以同时预览多个文件了 如果想全屏预览,则在1中,按住 option+空格键 ,然后再进行2 ,就实现全屏预览了 ...

  8. 安卓开发实战-记账本APP(五)

    今天将昨天剩余的bug修复,并且完成图标的美化,设置长按删除,模仿支付宝实现金额的动态增加. ①将昨天的布局进行了修改:之前是fragment,改成FrameLayout布局,不再设置name,进而在 ...

  9. 【动手学pytorch】softmax回归

    一.什么是softmax? 有一个数组S,其元素为Si ,那么vi 的softmax值,就是该元素的指数与所有元素指数和的比值.具体公式表示为: softmax回归本质上也是一种对数据的估计 二.交叉 ...

  10. Java学习笔记----打印基本数据类型范围

    /** * Created by N3verL4nd on 2016/11/10. */ public class HelloWorld { public static void main(Strin ...