Java多线程实战-CompletableFuture异步编程优化查询接口响应速度

🏷️个人主页:牵着猫散步的鼠鼠 

🏷️系列专栏:Java全栈-专栏

🏷️本系列源码仓库:多线程并发编程学习的多个代码片段(github)

🏷️个人学习笔记,若有缺误,欢迎评论区指正 

目录

前言

实现思路

CompletableFuture快速入门

1.创建CompletableFuture

2.链式调用

3.异常处理

4.组合多个CompletableFuture

5.设置超时时间

代码实现

1.初始化线程池

2.封装响应信息聚合对象

3.通过CompletableFuture异步执行每一个查询操作

4.测试

其他优化点

总结


✨️本系列源码均已上传仓库 1321928757/Concurrent-MulThread-Demo(github.com)✨️

前言

在Web应用开发中,一个界面可能需要同时请求多个接口来获取不同信息。传统的做法是编写一个聚合接口同步获取这些数据,第二种方法是分多次请求来获取数据。这两种方式虽然简单直观,但效率比较低下,随着应用复杂度的增加,这种低效的做法将会带来严重的性能问题。

异步编程模型可以很好地解决这个问题。多个任务可以同时执行,互不影响,从而大幅提高应用的响应速度和吞吐量。Java 8 中引入的CompletableFuture为异步编程提供了强有力的支持,使得编写异步代码变得更加简单。本文将重点介绍如何利用CompletableFuture优化并发查询接口的响应速度。

实现思路

要优化并发查询接口的响应速度,传统的优化方式是通过多线程来并行执行多个查询任务。但这种做法存在一些缺陷:

  1. 创建和管理线程的开销较大,如果线程数量过多,会给系统带来很大的压力。
  2. 如果查询任务的执行时间不均匀,会导致部分线程需要长时间等待,资源利用率低下。

而CompletableFuture提供了一种更优雅、更高效的解决方案。其核心思路是:

  1. 每个查询任务都封装为一个CompletableFuture异步任务,由线程池并行执行。
  2. 通过CompletableFuture.allOf()方法等待所有异步任务完成。
  3. 最后从每个任务的结果中组装出最终需要的数据对象。

CompletableFuture快速入门

在JDK8以后,CompletableFuture提供了丰富的API用于异步编程,下面列举了一些最常见的用法:

1.创建CompletableFuture

有多种方式可以创建CompletableFuture:

// 从一个供给函数创建
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");// 从一个运行函数创建 
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> System.out.println("Hello"));// 从一个已有的结果创建
CompletableFuture<String> future = CompletableFuture.completedFuture("Hello");

2.链式调用

CompletableFuture支持链式调用,可以方便地对异步结果进行转换和组合:

CompletableFuture<String> resultFuture = CompletableFuture.supplyAsync(() -> "Hello").thenApply(s -> s + " World") // 对结果进行转换.thenCompose(s -> getResult(s)); // 组合另一个异步操作

3.异常处理

通过exceptionally()方法可以对异常情况进行处理:

String result = CompletableFuture.supplyAsync(() -> {throw new RuntimeException("error"); 
}).exceptionally(ex -> {// 处理异常return "Default Value";
}).get();

4.组合多个CompletableFuture

通过allOf,anyOf这两种方式我们可以让任务之间协同工作,join()和get()方法都是阻塞调用它们的线程(通常为主线程)来获取CompletableFuture异步之后的返回值。

get() 方法会抛出经检查的异常,可被捕获,自定义处理或者直接抛出。

而 join() 会抛出未经检查的异常。

// 等待所有任务完成
CompletableFuture.allOf(future1, future2, future3).get();
CompletableFuture.allOf(future1, future2, future3).join();// 只要任意一个任务完成即可  
CompletableFuture.anyOf(future1, future2, future3).get();
CompletableFuture.anyOf(future1, future2, future3).join();// 规定超时时间,防止一直堵塞
CompletableFuture.allOf(future1, future2, future3).get(6, TimeUnit.SECONDS);

5.设置超时时间

我们可以通过下面的方式可以设置某个CompletableFuture的超时时间:

String result = CompletableFuture.supplyAsync(() -> "Hello").completeOnTimeout("Timeout!", 1, TimeUnit.SECONDS).get();

代码实现

1.初始化线程池

application.yaml配置文件

# 线程池配置
thread:pool:corePoolSize: 10maxPoolSize: 20queueCapacity: 100keepAliveSeconds: 60

线程池配置类ThreadPoolConfig

/*** @author Luckysj @刘仕杰* @description 线程池配置* @create 2024/03/19 21:43:57*/
@Configuration
public class ThreadPoolConfig {@Value("${thread.pool.corePoolSize}")private int corePoolSize;@Value("${thread.pool.maxPoolSize}")private int maxPoolSize;@Value("${thread.pool.queueCapacity}")private int queueCapacity;@Value("${thread.pool.keepAliveSeconds}")private int keepAliveSeconds;@Beanpublic ThreadPoolExecutor threadPoolExecutor() {return new ThreadPoolExecutor(corePoolSize,maxPoolSize,keepAliveSeconds,TimeUnit.SECONDS,new LinkedBlockingQueue<>(queueCapacity),new ThreadPoolExecutor.CallerRunsPolicy());}
}

2.封装响应信息聚合对象

我们这里模拟用户相关的界面,这里需要点赞数,粉丝数,文章数等信息

/*** @author Luckysj @刘仕杰* @description 信息聚合对象* @create 2024/03/19 21:48:13*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserBehaviorDataDTO {//用户IDprivate Long userId ;//发布文章数private Long articleCount ;//点赞数private Long likeCount ;//粉丝数private Long fansCount ;//消息数private Long msgCount ;//收藏数private Long collectCount ;//关注数private Long followCount ;//红包数private Long redBagCount ;// 卡券数private Long couponCount ;}

3.通过CompletableFuture异步执行每一个查询操作

如下,我们定义了一个异步任务类,创建每一个查询操作的CompletableFuture异步任务放入线程中执行,并利用allOf等待全部任务执行完成,执行完成后组装查询信息到聚合对象中返回

/*** @author Luckysj @刘仕杰* @description 一个页面可能有多达10个左右的一个用户行为数据,我们可以通过多线程来提高查询速率* @create 2024/03/19 21:45:04*/
@Slf4j
@Component
public class MyFutureTask {@ResourceUserService userService;// 线程池@Resourceprivate ExecutorService executor;public UserBehaviorDataDTO getUserAggregatedResult(final Long userId) {System.out.println("MyFutureTask的线程:" + Thread.currentThread());try {// 1.发布文章数CompletableFuture<Long> articleCountFT = CompletableFuture.supplyAsync(() -> userService.countArticleCountByUserId(userId), executor);// 2.点赞数CompletableFuture<Long> LikeCountFT = CompletableFuture.supplyAsync(() -> userService.countLikeCountByUserId(userId), executor);// 3.粉丝数CompletableFuture<Long> fansCountFT = CompletableFuture.supplyAsync(() -> userService.countFansCountByUserId(userId), executor);// 4.消息数CompletableFuture<Long> msgCountFT = CompletableFuture.supplyAsync(() -> userService.countMsgCountByUserId(userId), executor);// 5.收藏数CompletableFuture<Long> collectCountFT = CompletableFuture.supplyAsync(() -> userService.countCollectCountByUserId(userId), executor);// 6.关注数CompletableFuture<Long> followCountFT = CompletableFuture.supplyAsync(() -> userService.countFollowCountByUserId(userId), executor);// 7.红包数CompletableFuture<Long> redBagCountFT = CompletableFuture.supplyAsync(() -> userService.countRedBagCountByUserId(userId), executor);// 8.卡券数CompletableFuture<Long> couponCountFT = CompletableFuture.supplyAsync(() -> userService.countCouponCountByUserId(userId), executor);// 等待全部线程执行完毕 这里一定要设超时时间,不然会一直等待CompletableFuture.allOf(articleCountFT, LikeCountFT, fansCountFT, msgCountFT, collectCountFT, followCountFT, redBagCountFT, couponCountFT).get(6, TimeUnit.SECONDS);// 必须设置合理的超时时间UserBehaviorDataDTO userBehaviorData = UserBehaviorDataDTO.builder().articleCount(articleCountFT.get()).likeCount(LikeCountFT.get()).fansCount(fansCountFT.get()).msgCount(msgCountFT.get()).collectCount(collectCountFT.get()).followCount(followCountFT.get()).redBagCount(redBagCountFT.get()).couponCount(couponCountFT.get()).build();return userBehaviorData;} catch (Exception e) {log.error("get user behavior data error", e);return new UserBehaviorDataDTO();}}

这里用户服务类中我采用线程睡眠来模拟查询耗时 

4.测试

访问测试接口,日志输出如下:

UserController的线程:Thread[http-nio-8080-exec-2,5,main]
MyFutureTask的线程:Thread[http-nio-8080-exec-2,5,main]
UserService获取ArticleCount的线程  pool-2-thread-1
UserService获取likeCount的线程  pool-2-thread-2
UserService获取MsgCount的线程  pool-2-thread-4
UserService获取CollectCount的线程  pool-2-thread-5
UserService获取FollowCount的线程  pool-2-thread-6
UserService获取RedBagCount的线程  pool-2-thread-7
UserService获取CouponCount的线程  pool-2-thread-8
获取CouponCount===睡眠:0s
获取RedBagCount===睡眠:1s
获取FollowCount===睡眠:1s
获取CollectCount==睡眠:2s
获取FansCount===睡眠:1s
UserService获取FansCount的线程  pool-2-thread-3
获取ArticleCount===睡眠:1s
获取MsgCount===睡眠:1s
获取likeCount===睡眠:2s
===============总耗时:2.019秒

可以看到,总耗时主要取决于耗时最长的那个操作,相比于串行查询肯定快多了 

其他优化点

除了使用CompletableFuture并行查询优化外,还有以下可以提高接口查询速率的方法:

  • 数据缓存: 对于一些常用且不经常变动的数据,可以考虑加入redis缓存或者本地缓存,减少数据库查询。
  • 异步持久化: 对于一些不需要立即写入数据库的数据,可以先放入消息队列,由后台程序异步处理,减轻数据库压力。
  • 分库分表: 对于数据量较大的表,可以考虑分库分表,避免单表数据量过大带来的查询效率问题。

总结

CompletableFuture为Java提供了强大的异步编程能力,可以极大地提高应用的并发能力和响应速度。通过并行执行多个查询任务,我们可以大幅减少接口的响应时间,优化用户体验。同时,CompletableFuture的代码风格函数式、简洁、优雅,也使得代码更加易读易维护。

但是,异步编程也不是万能的,它需要开发者转变思维模式,还需要权衡利弊。在实际项目中,我们可以结合其他优化手段,选择合适的方案,以达到最佳的性能效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/765357.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言——程序拷贝文件

问题如下&#xff1a; 写一个程序拷贝文件&#xff1a; 使用所学文件操作&#xff0c;在当前目录下放一个文件data.txt&#xff0c;写一个程序&#xff0c;将data.txt文件拷贝一份&#xff0c;生成data_copy.txt文件。 基本思路&#xff1a; 打开文件data.txt&#xff0c;读…

SG5032VAN差分晶振X1G004261001100专用于5G通讯设备

差分晶体振荡器(DXO)是目前行业中公认高技术&#xff0c;高要求的一款晶体振荡器&#xff0c;是指输出差分信号使用2种相位彼此完全相反的信号,从而消除了共模噪声,并产生一个更高性能的系统。差分晶振一般为六脚贴片晶振&#xff0c;输出类型分为好几种,LVDS&#xff0c;LV-PE…

力扣面试150 阶乘后的零 数论 找规律 质因数

Problem: 172. 阶乘后的零 思路 &#x1f468;‍&#x1f3eb; 大佬神解 一个数末尾有多少个 0 &#xff0c;取决于这个数 有多少个因子 10而 10 可以分解出质因子 2 和 5而在阶乘种&#xff0c;2 的倍数会比 5 的倍数多&#xff0c;换而言之&#xff0c;每一个 5 都会找到一…

Linux初学(八)磁盘管理

一、磁盘管理 1.1 简介 磁盘的工作原理&#xff1a; 添加磁盘对磁盘进行分区格式化磁盘挂载和使用磁盘 磁盘的类型&#xff1a; 固态机械 磁盘的接口类型&#xff1a; IDESTSTSCSI 磁盘工作的原理&#xff1a; 磁盘&#xff0c;特别是硬盘&#xff0c;和内存不同&#xff0c;…

目标检测——PP-YOLO算法解读

PP-YOLO系列&#xff0c;均是基于百度自研PaddlePaddle深度学习框架发布的算法&#xff0c;2020年基于YOLOv3改进发布PP-YOLO&#xff0c;2021年发布PP-YOLOv2和移动端检测算法PP-PicoDet&#xff0c;2022年发布PP-YOLOE和PP-YOLOE-R。由于均是一个系列&#xff0c;所以放一起解…

【JavaEE初阶系列】——带你了解volatile关键字以及wait()和notify()两方法背后的原理

目录 &#x1f6a9;volatile关键字 &#x1f388;volatile 不保证原子性 &#x1f388;synchronized 也能保证内存可见性 &#x1f388;Volatile与Synchronized比较 &#x1f6a9;wait和notify &#x1f388;wait()方法 &#x1f4bb;wait(参数)方法 &#x1f388;noti…

【单元测试】一文读懂java单元测试

目录 1. 什么是单元测试2. 为什么要单元测试3. 单元测试框架 - JUnit3.1 JUnit 简介3.2 JUnit 内容3.3 JUnit 使用3.3.1 Controller 层单元测试3.3.2 Service 层单元测试3.3.3 Dao 层单元测试3.3.4 异常测试3.3.5 测试套件测多个类3.3.6 idea 中查看单元测试覆盖率3.3.7 JUnit …

第28章 ansible的使用

第28章 ansible的使用 本章主要介绍在 RHEL8 中如何安装 ansible 及 ansible的基本使用。 ◆ ansible 是如何工作的 ◆ 在RHEL8 中安装ansible ◆ 编写 ansible.cfg 和清单文件 ◆ ansible 的基本用法 文章目录 第28章 ansible的使用28.1 安装ansible28.2 编写ansible.cfg和清…

HDFS集群环境配置

环境如下三台服务器&#xff1a; 192.168.32.101 node1192.168.32.102 node2192.168.32.103 node3 一、Hadoop安装包下载&#xff0c;点此官网下载 二、Hadoop HDFS的角色包含&#xff1a; NameNode&#xff0c;主节点管理者DataNode&#xff0c;从节点工作者SecondaryNameN…

React Native: could not connect to development server

问题&#xff1a; 运行模拟器错误&#xff1a;无法连接到开发服务器 原因分析&#xff1a; 1、确认模拟器连接状态&#xff0c;是连接成功的 查看进程的端口占用&#xff0c;也没问题 lsof -i tcp:8081 kill pid2、检查包服务器是否运行正常 连接真机进行调试发现真机是正常…

基于springboot+vue+Mysql的“智慧食堂”设计与实现

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

Basic RNN

文章目录 回顾RNNRNN CellRNNCell的使用RNN的使用 RNN例子使用RNN Cell实现使用RNN实现 嵌入层 Embedding独热向量的缺点Embedding LSTMGRU(门控循环单元)练习 回顾 DNN&#xff08;全连接&#xff09;&#xff1a;和CNN相比&#xff0c;拥有巨大的参数量&#xff0c;CNN权重共…

游泳耳机哪个牌子好?强烈推荐这4大高性能款式!

在如今的科技时代&#xff0c;游泳耳机已经成为了许多游泳爱好者和运动员的必备装备。一款好的游泳耳机不仅可以让你在水中享受到美妙的音乐&#xff0c;还可以为你提供更好的训练体验。 &#xff08;下图是我测试过的一部分游泳耳机&#xff1a;&#xff09; 但在市场上众多的…

在线文本列表差集计算器

具体请前往&#xff1a;在线文本差集计算工具

云计算 3月22号 (mysql的主从复制)

一、MySQL-Replication&#xff08;主从复制&#xff09; 1.1、MySQL Replication 主从复制&#xff08;也称 AB 复制&#xff09;允许将来自一个MySQL数据库服务器&#xff08;主服务器&#xff09;的数据复制到一个或多个MySQL数据库服务器&#xff08;从服务器&#xff09;…

优化选址问题 | 基于禁忌搜索算法求解基站选址问题含Matlab源码

目录 问题代码问题 禁忌搜索算法(Tabu Search)是一种局部搜索算法的扩展,它通过引入一个禁忌列表来避免陷入局部最优解,并允许在一定程度上接受较差的解来跳出局部最优。在基站选址问题中,我们可以使用禁忌搜索算法来寻找满足覆盖要求且基站数量最少的选址方案。 以下是…

比赛记录:Codeforces Round 936 (Div. 2) A~E

传送门:CF [前题提要]:赛时一小时过了A~D,E感觉也不是很难(甚至感觉思维难度是小于D的),感觉这回是自己不够自信了,要是自信一点深入想一下应该也能做出来,咱就是说,如果E和D换一下,结果也是一样的,虽上大分,但是心里很不服,故记录一下 A - Median of an Array 当时网卡加载了…

手机网页视频批量提取工具可导出视频分享链接|爬虫采集下载软件

解放你的抖音视频管理——全新抖音批量下载工具震撼上线&#xff01; 在这个信息爆炸的时代&#xff0c;如何高效地获取、管理和分享视频内容成为了许多用户的迫切需求。为了解决这一难题&#xff0c;我们研发了全新的视频批量下载工具&#xff0c;让你轻松畅享海量音视频资源。…

SQL中条件放在on后与where后的区别

数据库在通过连接两张或多张表来返回记录时&#xff0c;都会生成一张中间的临时表&#xff0c;然后再将这张临时表返回给用户。 在使用left jion时&#xff0c;on和where条件的区别如下&#xff1a; on条件是在生成临时表时使用的条件&#xff0c;不管on中的条件是否为真&…

2024年 前端JavaScript Web APIs 第四天 笔记

4.1-日期对象的使用 4.2-时间戳的使用 4.3-倒计时案例的制作 4.4-查找DOM节点 4.5-增加节点以及学成在线案例 4.6-克隆节点和删除节点 4.7-M端事件 4.8-swiper插件的使用 4.9-今日综合案例-学生信息表 B站 <!DOCTYPE html> <html lang"en"><head>&…