java future 性能_实践:使用了CompletableFuture之后,程序性能提升了三倍

CompletableFuture

相比于jdk5所提出的future概念,future在执行的时候支持异步处理,但是在回调的过程中依旧是难免会遇到需要等待的情况。

在jdk8里面,出现了CompletableFuture的新概念,支持对于异步处理完成任务之后自行处理数据。当发生异常的时候也能按照自定义的逻辑来处理。

如何通过使用CompletableFuture提升查询的性能呢?

下边我举个例子来演示:

首先我们定义一个UserInfo对象:

/***@authoridea

* @data 2020/2/22*/

public classUserInfo {privateInteger id;privateString name;privateInteger jobId;privateString jobDes;privateInteger carId;privateString carDes;privateInteger homeId;privateString homeDes;publicInteger getId() {returnid;

}publicUserInfo setId(Integer id) {this.id =id;return this;

}publicString getName() {returnname;

}publicUserInfo setName(String name) {this.name =name;return this;

}publicInteger getJobId() {returnjobId;

}publicUserInfo setJobId(Integer jobId) {this.jobId =jobId;return this;

}publicString getJobDes() {returnjobDes;

}publicUserInfo setJobDes(String jobDes) {this.jobDes =jobDes;return this;

}publicInteger getCarId() {returncarId;

}publicUserInfo setCarId(Integer carId) {this.carId =carId;return this;

}publicString getCarDes() {returncarDes;

}publicUserInfo setCarDes(String carDes) {this.carDes =carDes;return this;

}publicInteger getHomeId() {returnhomeId;

}publicUserInfo setHomeId(Integer homeId) {this.homeId =homeId;return this;

}publicString getHomeDes() {returnhomeDes;

}publicUserInfo setHomeDes(String homeDes) {this.homeDes =homeDes;return this;

}

}

这个对象里面的homeid,jobid,carid都是用于匹配对应的住房信息描述,职业信息描述,购车信息描述。

对于将id转换为描述信息的方式需要通过额外的sql查询,这里做了个简单的工具类来进行模拟:

importjava.util.concurrent.TimeUnit;importjava.util.function.Supplier;/***@authoridea

* @data 2020/2/22*/

public classQueryUtils {publicString queryCar(Integer carId){try{

TimeUnit.SECONDS.sleep(1);

}catch(InterruptedException e) {

e.printStackTrace();

}return "car_desc";

}publicString queryJob(Integer jobId){try{

TimeUnit.SECONDS.sleep(1);

}catch(InterruptedException e) {

e.printStackTrace();

}return "job_desc";

}publicString 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接口,根据注入的类型进行转译查询:

importjava.util.function.Supplier;public class QuerySuppiler implements Supplier{privateInteger id;privateString type;privateQueryUtils queryUtils;publicQuerySuppiler(Integer id, String type,QueryUtils queryUtils) {this.id =id;this.type =type;this.queryUtils=queryUtils;

}

@OverridepublicString get() {if("home".equals(type)){returnqueryUtils.queryHome(id);

}else if ("job".equals(type)){returnqueryUtils.queryJob(id);

}else if ("car".equals(type)){returnqueryUtils.queryCar(id);

}return null;

}

}

由于对应的carid,homeid,jobid都需要到指定的k,v配置表里面通过核心查询包装器来进行转译,因此通常的做法就是在for循环里面一个个地进行遍历解析,这样的做法也比较易于理解。

QuerySuppiler 是我写的一个用于做对象解析的服务,代码如下所示:

importjava.util.ArrayList;importjava.util.Collections;importjava.util.List;importjava.util.concurrent.CompletableFuture;importjava.util.function.Consumer;importjava.util.function.Supplier;importjava.util.stream.Collectors;/***@authoridea

* @data 2020/2/22*/

public classQueryUserService {private Supplier queryUtilsSupplier = QueryUtils::new;publicUserInfo converUserInfo(UserInfo userInfo) {

QuerySuppiler querySuppiler1= new QuerySuppiler(userInfo.getCarId(), "car", queryUtilsSupplier.get());

CompletableFuture getCarDesc =CompletableFuture.supplyAsync(querySuppiler1);

getCarDesc.thenAccept(new Consumer() { --1@Overridepublic voidaccept(String carDesc) {

userInfo.setCarDes(carDesc);

}

});

QuerySuppiler querySuppiler2= new QuerySuppiler(userInfo.getHomeId(), "home", queryUtilsSupplier.get());

CompletableFuture getHomeDesc =CompletableFuture.supplyAsync(querySuppiler2);

getHomeDesc.thenAccept(new Consumer() { --2@Overridepublic voidaccept(String homeDesc) {

userInfo.setHomeDes(homeDesc);

}

});

QuerySuppiler querySuppiler3= new QuerySuppiler(userInfo.getJobId(), "job", queryUtilsSupplier.get());

CompletableFuture getJobDesc =CompletableFuture.supplyAsync(querySuppiler3);

getJobDesc.thenAccept(new Consumer() { --3@Overridepublic voidaccept(String jobDesc) {

userInfo.setJobDes(jobDesc);

}

});

CompletableFuture getUserInfo =CompletableFuture.allOf(getCarDesc, getHomeDesc, getJobDesc);

getUserInfo.thenAccept(new Consumer() {

@Overridepublic voidaccept(Void result) {

System.out.println("全部完成查询");

}

});

getUserInfo.join();--4

returnuserInfo;

}public static voidmain(String[] args) {long begin=System.currentTimeMillis();//多线程环境需要注意线程安全问题

List userInfoList=Collections.synchronizedList(new ArrayList<>());for(int i=0;i<=20;i++){

UserInfo userInfo=newUserInfo();

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=newQueryUserService();

userInfo=queryUserService.converUserInfo(userInfo);returnuserInfo;

}).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 来优化查询

*

*@paramuserInfo

*@return

*/

publicUserInfo converUserInfoV2(UserInfo userInfo) {

Callable homeCallable=newCallable() {

@Overridepublic Object call() throwsException {returnqueryUtilsSupplier.get().queryHome(userInfo.getHomeId());

}

};

FutureTask getHomeDesc=new FutureTask<>(homeCallable);newThread(getHomeDesc).start();

futureMap.put("homeCallable",getHomeDesc);

Callable carCallable=newCallable() {

@Overridepublic Object call() throwsException {returnqueryUtilsSupplier.get().queryCar(userInfo.getCarId());

}

};

FutureTask getCarDesc=newFutureTask(carCallable);newThread(getCarDesc).start();

futureMap.put("carCallable",getCarDesc);

Callable jobCallable=newCallable() {

@Overridepublic Object call() throwsException {returnqueryUtilsSupplier.get().queryCar(userInfo.getJobId());

}

};

FutureTask getJobDesc=new FutureTask<>(jobCallable);newThread(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("该对象完成查询");returnuserInfo;

}

经过测试,使用 futuretask 进行优化的查询结果只有47s左右,远远不及CompletableFuture的性能高效.这是因为使用了futuretask的get方法依然是存在堵塞的情况。

关键部分看这段内容:

userInfo.setHomeDes((String) futureMap.get("homeCallable").get()); --1userInfo.setCarDes((String)futureMap.get("carCallable").get()); --2userInfo.setJobDes((String)futureMap.get("jobCallable").get()); --3

—1代码在执行的时候遇到了堵塞,然后—2和—3的get也需要进行等待,因此使用常规的futuretask进行优化,这里难免还是会有堵塞的情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值