实践:使用了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 ...
随机推荐
- 自定义博客cnblogs样式的必备前端小知识——css
css样式相关小知识 文字超出一行显示省略号 overflow: hidden; /*自动隐藏文字*/ text-overflow: ellipsis; /*文字隐藏后添加省略号*/ white-sp ...
- CodeSign error: no provisioning profile at path '/Users/zhht-2015/Library/MobileDevice/Provisioning Profiles/79693141-f98b-4ac4-8bb4-476c9475f265.mobileprovision'
解决方法: 1.关闭Xcode,找到项目中的**.xcodeproj文件,点击右键,show package contents(打开包内容). 2.打开后找到project.pbxproj文件,用文本 ...
- Xhemj的Minecraft皮肤信息
xhemj Minecraft Profile UUID:086e0354-fbb6-446b-83d4-60bdf449ad4e UUID:086e0354fbb6446b83d460bdf449a ...
- 【原创】CentOS8双网卡绑定
1. NAT网络配置(所有服务器): # yum install bash-completion # cd /etc/sysconfig/network-scripts/ bond0配置: # vim ...
- 51Nod 1238 - 最小公倍数之和 V3(毒瘤数学+杜教筛)
题目 戳这里 推导 ∑i=1n∑j=1nlcm(i,j)~~~\sum_{i=1}^{n}\sum_{j=1}^{n}lcm(i,j) ∑i=1n∑j=1nlcm(i,j) =∑i=1n∑j= ...
- Python学习,第七课 - 文件操作
Python中对文件的相关操作详解 文件的操作在今后的Python开发中也是使用非常频繁的. 先说下对文件操作的流程 打开文件,得到文件的一个句柄,赋值给一个变量 然后通过句柄对文件进行操作(内容的增 ...
- Linux守护进程之systemd
介绍 历史上,Linux 的启动一直采用init进程:下面的命令用来启动服务. $ sudo /etc/init.d/apache2 start # 或者 $ service apache2 star ...
- springboot中使用Caffeine本地缓存
Caffeine是使用Java8对Guava缓存的重写版本性能有很大提升 一 依赖 <dependency> <groupId>org.springframework.boot ...
- Tornadofx学习笔记(2)——FxRecyclerView控件的打造
Tornadofx是基于javafx的一个kotlin框架,用来写些电脑版的小程序 基于Scroll Pane控件,仿造Android中的RecyclerView,实现的一款tornadofx的控件 ...
- 注册并加入dn42网络的方法
简介 https://dn42.net/howto/Getting-started 注册要求: 一个24小时运行的linux/BSD设备 该设备必须支持创建隧道,例如GRE,OpenVpn,IPSec ...