CompletableFuture

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

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

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

下边我举个例子来演示:

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

  1. /**
  2. * @author idea
  3. * @data 2020/2/22
  4. */
  5. public class UserInfo {
  6. private Integer id;
  7. private String name;
  8. private Integer jobId;
  9. private String jobDes;
  10. private Integer carId;
  11. private String carDes;
  12. private Integer homeId;
  13. private String homeDes;
  14. public Integer getId() {
  15. return id;
  16. }
  17. public UserInfo setId(Integer id) {
  18. this.id = id;
  19. return this;
  20. }
  21. public String getName() {
  22. return name;
  23. }
  24. public UserInfo setName(String name) {
  25. this.name = name;
  26. return this;
  27. }
  28. public Integer getJobId() {
  29. return jobId;
  30. }
  31. public UserInfo setJobId(Integer jobId) {
  32. this.jobId = jobId;
  33. return this;
  34. }
  35. public String getJobDes() {
  36. return jobDes;
  37. }
  38. public UserInfo setJobDes(String jobDes) {
  39. this.jobDes = jobDes;
  40. return this;
  41. }
  42. public Integer getCarId() {
  43. return carId;
  44. }
  45. public UserInfo setCarId(Integer carId) {
  46. this.carId = carId;
  47. return this;
  48. }
  49. public String getCarDes() {
  50. return carDes;
  51. }
  52. public UserInfo setCarDes(String carDes) {
  53. this.carDes = carDes;
  54. return this;
  55. }
  56. public Integer getHomeId() {
  57. return homeId;
  58. }
  59. public UserInfo setHomeId(Integer homeId) {
  60. this.homeId = homeId;
  61. return this;
  62. }
  63. public String getHomeDes() {
  64. return homeDes;
  65. }
  66. public UserInfo setHomeDes(String homeDes) {
  67. this.homeDes = homeDes;
  68. return this;
  69. }
  70. }

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

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

  1. import java.util.concurrent.TimeUnit;
  2. import java.util.function.Supplier;
  3. /**
  4. * @author idea
  5. * @data 2020/2/22
  6. */
  7. public class QueryUtils {
  8. public String queryCar(Integer carId){
  9. try {
  10. TimeUnit.SECONDS.sleep(1);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. return "car_desc";
  15. }
  16. public String queryJob(Integer jobId){
  17. try {
  18. TimeUnit.SECONDS.sleep(1);
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. return "job_desc";
  23. }
  24. public String queryHome(Integer homeId){
  25. try {
  26. TimeUnit.SECONDS.sleep(1);
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. return "home_desc";
  31. }
  32. }

这个工具类的功能看起来会比较通俗易懂,在常规的逻辑里面,我们做批量对象的转换大多数都是基于List遍历,然后在循环里面批量查询,这样的方式并非说不行,而是显得比较过于“暴力”。

假设每次查询需要消耗1s,那么遍历的一个size为n的集合查询消耗的时间就是n * 3s。

下边来介绍一种更为方便的技巧:CompletableFuture

定义一个QuerySupplier 实现Supplier接口,根据注入的类型进行转译查询:

  1. import java.util.function.Supplier;
  2. public class QuerySuppiler implements Supplier<String> {
  3. private Integer id;
  4. private String type;
  5. private QueryUtils queryUtils;
  6. public QuerySuppiler(Integer id, String type,QueryUtils queryUtils) {
  7. this.id = id;
  8. this.type = type;
  9. this.queryUtils=queryUtils;
  10. }
  11. @Override
  12. public String get() {
  13. if("home".equals(type)){
  14. return queryUtils.queryHome(id);
  15. }else if ("job".equals(type)){
  16. return queryUtils.queryJob(id);
  17. }else if ("car".equals(type)){
  18. return queryUtils.queryCar(id);
  19. }
  20. return null;
  21. }
  22. }

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

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

  1. import java.util.ArrayList;
  2. import java.util.Collections;
  3. import java.util.List;
  4. import java.util.concurrent.CompletableFuture;
  5. import java.util.function.Consumer;
  6. import java.util.function.Supplier;
  7. import java.util.stream.Collectors;
  8. /**
  9. * @author idea
  10. * @data 2020/2/22
  11. */
  12. public class QueryUserService {
  13. private Supplier<QueryUtils> queryUtilsSupplier = QueryUtils::new;
  14. public UserInfo converUserInfo(UserInfo userInfo) {
  15. QuerySuppiler querySuppiler1 = new QuerySuppiler(userInfo.getCarId(), "car", queryUtilsSupplier.get());
  16. CompletableFuture<String> getCarDesc = CompletableFuture.supplyAsync(querySuppiler1);
  17. getCarDesc.thenAccept(new Consumer<String>() { --1
  18. @Override
  19. public void accept(String carDesc) {
  20. userInfo.setCarDes(carDesc);
  21. }
  22. });
  23. QuerySuppiler querySuppiler2 = new QuerySuppiler(userInfo.getHomeId(), "home", queryUtilsSupplier.get());
  24. CompletableFuture<String> getHomeDesc = CompletableFuture.supplyAsync(querySuppiler2);
  25. getHomeDesc.thenAccept(new Consumer<String>() { --2
  26. @Override
  27. public void accept(String homeDesc) {
  28. userInfo.setHomeDes(homeDesc);
  29. }
  30. });
  31. QuerySuppiler querySuppiler3 = new QuerySuppiler(userInfo.getJobId(), "job", queryUtilsSupplier.get());
  32. CompletableFuture<String> getJobDesc = CompletableFuture.supplyAsync(querySuppiler3);
  33. getJobDesc.thenAccept(new Consumer<String>() { --3
  34. @Override
  35. public void accept(String jobDesc) {
  36. userInfo.setJobDes(jobDesc);
  37. }
  38. });
  39. CompletableFuture<Void> getUserInfo = CompletableFuture.allOf(getCarDesc, getHomeDesc, getJobDesc);
  40. getUserInfo.thenAccept(new Consumer<Void>() {
  41. @Override
  42. public void accept(Void result) {
  43. System.out.println("全部完成查询" );
  44. }
  45. });
  46. getUserInfo.join(); --4
  47. return userInfo;
  48. }
  49. public static void main(String[] args) {
  50. long begin= System.currentTimeMillis();
  51. //多线程环境需要注意线程安全问题
  52. List<UserInfo> userInfoList=Collections.synchronizedList(new ArrayList<>());
  53. for(int i=0;i<=20;i++){
  54. UserInfo userInfo=new UserInfo();
  55. userInfo.setId(i);
  56. userInfo.setName("username"+i);
  57. userInfo.setCarId(i);
  58. userInfo.setJobId(i);
  59. userInfo.setHomeId(i);
  60. userInfoList.add(userInfo);
  61. }
  62. //stream 查询一个用户花费3s 并行计算后一个用户1秒左右 查询21个用户花费21秒
  63. //parallelStream 速度更慢
  64. userInfoList.stream()
  65. .map(userInfo->{
  66. QueryUserService queryUserService=new QueryUserService();
  67. userInfo =queryUserService.converUserInfo(userInfo);
  68. return userInfo;
  69. }).collect(Collectors.toList());
  70. System.out.println("=============");
  71. long end=System.currentTimeMillis();
  72. System.out.println(end-begin);
  73. }
  74. }

看看这段代码的—1,—2,—3部分,三个执行点的位置在使用了thenAccept组装数据之后,还是可以避开串行化获取数据的情况。只有在—4的位置才会发生堵塞。这样对于性能的提升效果更佳。

这里进行模拟测试,采用原始暴力手段查询所消耗的时间是20 * 3 =60秒,但是这里使用了CompletableFuture之后,查询的时间就会缩短为了21秒。

结果:

  1. 全部完成查询
  2. =============
  3. 21223

这是一种使用了空间换时间的思路,或许你会说,异步查询如果使用FutureTask是不是也可以呢。嗯嗯,是的,但是使用future有个问题,就是在于返回获取异步结果的时候需要有等待状态,这个等待的状态是需要消耗时间进行堵塞的。

这里我也做了关于使用普通FutureTask来执行查询优化的结果:

  1. /**
  2. * 使用 FutureTask 来优化查询
  3. *
  4. * @param userInfo
  5. * @return
  6. */
  7. public UserInfo converUserInfoV2(UserInfo userInfo) {
  8. Callable<String> homeCallable=new Callable() {
  9. @Override
  10. public Object call() throws Exception {
  11. return queryUtilsSupplier.get().queryHome(userInfo.getHomeId());
  12. }
  13. };
  14. FutureTask<String> getHomeDesc=new FutureTask<>(homeCallable);
  15. new Thread(getHomeDesc).start();
  16. futureMap.put("homeCallable",getHomeDesc);
  17. Callable<String> carCallable=new Callable() {
  18. @Override
  19. public Object call() throws Exception {
  20. return queryUtilsSupplier.get().queryCar(userInfo.getCarId());
  21. }
  22. };
  23. FutureTask<String> getCarDesc=new FutureTask(carCallable);
  24. new Thread(getCarDesc).start();
  25. futureMap.put("carCallable",getCarDesc);
  26. Callable<String> jobCallable=new Callable() {
  27. @Override
  28. public Object call() throws Exception {
  29. return queryUtilsSupplier.get().queryCar(userInfo.getJobId());
  30. }
  31. };
  32. FutureTask<String> getJobDesc=new FutureTask<>(jobCallable);
  33. new Thread(getJobDesc).start();
  34. futureMap.put("jobCallable",getJobDesc);
  35. try {
  36. userInfo.setHomeDes((String) futureMap.get("homeCallable").get());
  37. userInfo.setCarDes((String)futureMap.get("carCallable").get());
  38. userInfo.setJobDes((String)futureMap.get("jobCallable").get());
  39. } catch (Exception e) {
  40. e.printStackTrace();
  41. }
  42. System.out.println("该对象完成查询" );
  43. return userInfo;
  44. }

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

关键部分看这段内容:

  1. userInfo.setHomeDes((String) futureMap.get("homeCallable").get()); --1
  2. userInfo.setCarDes((String)futureMap.get("carCallable").get()); --2
  3. 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. 自定义博客cnblogs样式的必备前端小知识——css

    css样式相关小知识 文字超出一行显示省略号 overflow: hidden; /*自动隐藏文字*/ text-overflow: ellipsis; /*文字隐藏后添加省略号*/ white-sp ...

  2. 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文件,用文本 ...

  3. Xhemj的Minecraft皮肤信息

    xhemj Minecraft Profile UUID:086e0354-fbb6-446b-83d4-60bdf449ad4e UUID:086e0354fbb6446b83d460bdf449a ...

  4. 【原创】CentOS8双网卡绑定

    1. NAT网络配置(所有服务器): # yum install bash-completion # cd /etc/sysconfig/network-scripts/ bond0配置: # vim ...

  5. 51Nod 1238 - 最小公倍数之和 V3(毒瘤数学+杜教筛)

    题目 戳这里 推导 ∑i=1n∑j=1nlcm(i,j)~~~\sum_{i=1}^{n}\sum_{j=1}^{n}lcm(i,j)   ∑i=1n​∑j=1n​lcm(i,j) =∑i=1n∑j= ...

  6. Python学习,第七课 - 文件操作

    Python中对文件的相关操作详解 文件的操作在今后的Python开发中也是使用非常频繁的. 先说下对文件操作的流程 打开文件,得到文件的一个句柄,赋值给一个变量 然后通过句柄对文件进行操作(内容的增 ...

  7. Linux守护进程之systemd

    介绍 历史上,Linux 的启动一直采用init进程:下面的命令用来启动服务. $ sudo /etc/init.d/apache2 start # 或者 $ service apache2 star ...

  8. springboot中使用Caffeine本地缓存

    Caffeine是使用Java8对Guava缓存的重写版本性能有很大提升 一 依赖 <dependency> <groupId>org.springframework.boot ...

  9. Tornadofx学习笔记(2)——FxRecyclerView控件的打造

    Tornadofx是基于javafx的一个kotlin框架,用来写些电脑版的小程序 基于Scroll Pane控件,仿造Android中的RecyclerView,实现的一款tornadofx的控件 ...

  10. 注册并加入dn42网络的方法

    简介 https://dn42.net/howto/Getting-started 注册要求: 一个24小时运行的linux/BSD设备 该设备必须支持创建隧道,例如GRE,OpenVpn,IPSec ...