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.rhkb.cn/news/279706.html

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

相关文章

远程桌面安卓版下载 安卓远程控制免费版

远程桌面安卓版下载与安卓远程控制免费版的应用解析 随着移动互联网的快速发展&#xff0c;远程桌面应用逐渐成为了许多用户、特别是技术爱好者和商务人士的必备工具。它们不仅可以在电脑上实现远程控制&#xff0c;还能将这种功能延伸到移动设备上&#xff0c;如安卓手机和平…

R语言中的常用基础绘图函数 直方图,箱线图,条形图,散点图

目录 R语言中的绘图参数 绘图函数 1.plot函数绘制散点图 2.hist函数绘制直方图 如何修饰直方图? 如何在直方图上标注各组频数&#xff1f; 使用text函数把某些信息标注在直方图上 如何在直方图上添加概率密度曲线&#xff1f; 3.boxplot函数绘制箱线图 4.barplot函数…

一、MySQL基础学习

目录 1、MySQL启动2、MySQL客户端连接3、SQL3.1、SQL语句分类3.2、DDL&#xff08;数据库定义语言&#xff09;3.2.1、操作数据库3.2.2、操作数据表 3.3、DML&#xff08;数据库操作语言&#xff09;3.3.1、增加 insert into3.3.2、删除 delete3.3.3、修改 update 3.4、DQL&…

idea error java:compilation failed:internal java compiler error

idea中编译运行maven项目报错如下 idea error java:compilation failed:internal java compiler error 尝试如下操作 注意&#xff1a;jdk8 需要设置4个地方 1.首先打开File->Project Structure中的Project&#xff0c;将SDK和language level都设置一致&#xff0c;如下…

Jackson 2.x 系列【4】对象映射器 ObjectMapper

有道无术&#xff0c;术尚可求&#xff0c;有术无道&#xff0c;止于术。 本系列Jackson 版本 2.0.0 源码地址&#xff1a;https://gitee.com/pearl-organization/study-seata-demo 文章目录 1. 概述2. 案例演示2.1 创建对象2.2 写入2.3 读取 3. 泛型擦除 1. 概述 在前两篇文…

IO多分复用

#include<myhead.h> #define SER_PORT 8888 //服务器端口号 #define SER_IP "192.168.65.131" //服务器IPint main(int argc, const char *argv[]) {//1、创建一个套接字int sfd -1;sfd socket(AF_INET, SOCK_STREAM, 0); //参数1&#xff1a;…

北斗卫星引领农机春耕新时代

北斗卫星引领农机春耕新时代 随着现代科技的快速发展&#xff0c;北斗卫星成为了农业领域不可或缺的利器。在农机自动驾驶系统的引领下&#xff0c;农机正逐渐实现自主操作&#xff0c;为农民节省了大量的时间和精力&#xff0c;并最大限度地提高了农作物的产量和质量。 北斗…

30天拿下Rust之错误处理

概述 在软件开发领域&#xff0c;对错误的妥善处理是保证程序稳定性和健壮性的重要环节。Rust作为一种系统级编程语言&#xff0c;以其对内存安全和所有权的独特设计而著称&#xff0c;其错误处理机制同样体现了Rust的严谨与实用。在Rust中&#xff0c;错误处理通常分为两大类&…

词令微信小程序怎么添加到我的小程序?

微信小程序怎么添加到我的小程序&#xff1f; 1、找到并打开要添加的小程序&#xff1b; 2、打开小程序后&#xff0c;点击右上角的「…」 3、点击后底部弹窗更多选项&#xff0c;请找到并点击「添加到我的小程序」&#xff1b; 4、添加成功后&#xff0c;就可以在首页下拉我的…

迈入编程世界:C 语言初体验

写在前面&#xff1a;OK啊&#xff0c;前面我们介绍了C语言开发环境的搭建&#xff0c;今天我们再来初步的认识一下C语言&#xff0c;了解C语言的基础框架、数据的表现形式以及输入和输出。 文章特点&#xff1a;不会很正经&#xff0c;不会很学术&#xff0c;不会很理论&#…

J4G企业通讯ip电话 sip对讲主机 停车场对讲主机

J4G企业通讯ip电话 sip对讲主机 停车场对讲主机 SV-J4G 是一款企业级彩屏网络电话&#xff0c;具有高清语音&#xff0c;320x240 2.8英寸彩屏&#xff0c;支持千兆以太网&#xff0c;12个SIP账号&#xff0c;支持PoE供电&#xff0c;支持外接EHS无线耳机&#xff0c;三方电话会…

C++有关内存的那些事

个人主页&#xff1a;PingdiGuo_guo 收录转栏&#xff1a;C干货专栏 前言 本篇博客是讲解关于C内存的一些知识点的。 文章目录 前言 1.内存函数 1.1memcpy函数 1.2memmove函数 1.3 memset函数 2.各数据类型占用 2.1bool类型 2.2char类型 2.3short、int、long类型及整数…

京东商品信息采集API商品价格商品详情图主图抓取接口key(提供测试入口)

item_get 获得JD商品详情item_search 按关键字搜索商品item_search_img 按图搜索京东商品&#xff08;拍立淘&#xff09;item_search_shop 获得店铺的所有商品item_history_price 获取商品历史价格信息item_recommend 获取推荐商品列表buyer_order_list 获取购买到的商品订单列…

CMake笔记之GLOB和GLOB_RECURSE的使用方法

CMake笔记之GLOB和GLOB_RECURSE的使用方法 —— 杭州 2024-03-19 夜 文章目录 CMake笔记之GLOB和GLOB_RECURSE的使用方法1.GLOB使用方法2.GLOB对比GLOB_RECURSE 1.GLOB使用方法 在 CMake 中&#xff0c;file(GLOB ...) 命令用于将匹配特定模式的文件列表赋值给变量。这可以用…

MySQL中replace into详解、批量更新、不存在插入存在则更新、replace into的坑

文章目录 一、replace into原理二、replace into的三种形式三、replace into 使用案例3.1、replace into values3.1.1、只有主键且主键冲突3.1.2、有主键有唯一索引且主键冲突3.1.3、有主键有唯一索引且唯一索引冲突(有坑)3.1.4、有主键有唯一索引且与一条主键冲突与另一条唯一…

瑞_Redis_短信登录_基于Session实现登录流程

文章目录 项目介绍1 短信登录1.1 项目准备1.2 基于Session实现登录流程1.2.1 功能流程介绍1.2.1.1 发送短信验证码1.2.1.2 短信验证码登录、注册1.2.1.3 校验登录状态 1.2.2 实现发送短信验证码功能1.2.2.1 页面流程1.2.2.2 代码实现1.2.2.3 测试 1.2.3 实现短信验证码登录、注…

2024年蓝牙耳机哪个好?真人实测告诉你如何选购,避免后悔!

在繁忙的通勤途中&#xff0c;无线蓝牙耳机已成为我们摆脱线缆束缚、享受音乐的理想选择。面对众多品牌和型号&#xff0c;选择合适的耳机似乎并不简单。因此&#xff0c;我精心挑选了几款表现不错的蓝牙耳机&#xff0c;希望我的分享能为你提供有价值的参考。 一、如何挑选蓝牙…

搭建EMQX MQTT服务器(超详细)

MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;服务器是一种实现 MQTT 协议的服务器软件。MQTT 是一种轻量级的、发布/订阅模式的消息传输协议&#xff0c;通常用于物联网&#xff08;IoT&#xff09;应用中的设备通信。MQTT 服务器负责接收来自客户端的消息…

Java_13 反转字符串中的单词 III(方法一将String改为StringBuilder)

一、反转字符串中的单词 III 给定一个字符串 s &#xff0c;你需要反转字符串中每个单词的字符顺序&#xff0c;同时仍保留空格和单词的初始顺序。 示例 1&#xff1a; 输入&#xff1a;s "Lets take LeetCode contest" 输出&#xff1a;"steL ekat edoCteeL …

数据之谜:解读Facebook的用户行为

在当今数字化时代&#xff0c;社交媒体平台已经成为人们生活中不可或缺的一部分&#xff0c;而Facebook作为全球最大的社交网络之一&#xff0c;其背后隐藏着许多数据之谜。本文将深入探讨Facebook的用户行为&#xff0c;并试图解读其中的奥秘。 用户行为数据的收集 Facebook作…