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

news2024/11/26 4:26:09

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

🏷️系列专栏: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: 10
    maxPoolSize: 20
    queueCapacity: 100
    keepAliveSeconds: 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;

    @Bean
    public 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 {

    //用户ID
    private 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 {
    @Resource
    UserService userService;

    // 线程池
    @Resource
    private 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.coloradmin.cn/o/1531054.html

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

相关文章

Spring 6.0和SpringBoot 3.0有什么新特性?

Spring在2022年相继推出了Spring Framework 6.0和SpringBoot 3.0&#xff0c;Spring把这次升级称之为新一代框架的开始&#xff0c;下一个10年的新开端 一、问题解析 主要更新内容是以下几个&#xff1a; ● A Java 17 baseline ● Support for Jakarta EE 10 with an EE 9 ba…

永磁同步电机无位置传感器系列(1)——非线性磁链观测器的仿真复现过程

无位置传感器控制&#xff0c;这个方向也是电机控制里面的大热门了。最近在看PLL&#xff0c;中国电机有一篇ESO-PLL获得了23年的高影响力论文&#xff0c;标题如下。因为这篇ESO-PLL需要用到下面这篇英文文献的转子位置观测器&#xff0c;想着就先把这个观测器的文章给复现了。…

【工具】vscode终端打不开

问题 1The terminal process failed to launch: A native exception occurred during launch (forkpty(3) failed.). 参考方案 下面参考链接是针对windows系统上vscode 出现的相同问题的解答 参考链接&#xff1a;https://blog.csdn.net/weixin_40921421/article/details/122…

农业四情监测设备—全面、准确地收集农田环境数据

型号推荐&#xff1a;云境天合TH-Q3】农业四情监测设备是一种高科技的农田监测工具&#xff0c;旨在实时监测和管理农田中的土壤墒情、作物生长、病虫害以及气象条件。这些设备综合运用了传感器、摄像头、气象站等技术手段&#xff0c;能够全面、准确地收集农田环境数据&#x…

机器学习之客户违约预测模型搭建之原理篇

前言 这一章主要介绍机器学习在金融领域一个重要应用&#xff1a;客户违约预测模型的搭建&#xff0c;其所用到原理为机器学习中的决策树模型。通过本章的学习&#xff0c;您能了解在信息时代下金融风险控制的新手段&#xff0c;并对机器学习有一个初步的了解。 1. 机器学习在…

【JavaScript】JavaScript 运算符 ⑤ ( 运算符优先级 )

文章目录 一、JavaScript 运算符优先级1、运算符优先级 概念2、运算符优先级 列举3、运算符示例 一、JavaScript 运算符优先级 1、运算符优先级 概念 JavaScript 的 运算符 是有 " 优先级 " 的 , " 运算符优先级 " 决定 一个表达式中 多个 运算符的 执行顺…

【每日一题】好子数组的最大分数

Tag 【单调栈】【暴力枚举】【数组】【2024-03-19】 题目来源 1793. 好子数组的最大分数 解题思路 本题和 84. 柱状图中最大的矩形 一样&#xff0c;计算的都是最大矩形的面积。只不过多了一个约束&#xff1a;矩形必须包含下标 k。 以下的方法一和方法二是 84. 柱状图中最…

你还在花钱看短剧吗?这些人做短剧推广已经通过短剧赚钱了

短剧分销&#xff0c;简单来说&#xff0c;就是你在抖音、快手、小红书等短视频平台浏览时看到的一半时&#xff0c;指引你去其他平台观看完整版的操作。 而使用“蜂小推”做短剧推广&#xff0c;你也能利用短剧快速致富~ 想要进行短剧分销&#xff0c;你需要经过一系列步骤&a…

23.python标准库之turtle库

一、窗体函数 turtle.setup(width, height, startx, starty) width:窗口宽度 height:窗口高度 startx:窗口与屏幕左侧距离&#xff08;单位象素&#xff09; starty:窗口与屏幕顶部距离&#xff08;单位象素&#xff09; 二、画笔状态函数 三、画笔运动函数

理清大数据技术与架构

大数据并不是一个系统软件&#xff0c;更不是一个单一的软件&#xff0c;它实际上是一种技术体系、一种数据处理方法&#xff0c;甚至可以说是一个服务平台。在这个技术体系中&#xff0c;涵盖了许多不同的部件&#xff0c;比如Hadoop服务平台。这一服务平台可以根据具体情况自…

Mybatis-Plus通用枚举介绍和使用

本文使用的是 Mybatis-Plus 从 3.4.2 版本来实现通用枚举配置&#xff0c;不同的版本&#xff0c;通用枚举配置会不一样&#xff0c;而且从Mybatis-Plus 从 3.5.2 版本开始只需使用 EnumValue 注解枚举属性&#xff0c;就可以实现一系列功能&#xff0c;而不需要其他配置。 这…

【python】python结合js逆向,让有道翻译成为你的翻译官,实现本地免费实时翻译

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN新星创作者等等。 🏆《博客》:Python全栈,前后端开发,人工智能,js逆向,A…

Spring Cloud 整合 GateWay

目录 第一章 微服务架构图 第二章 Spring Cloud整合Nacos集群 第三章 Spring Cloud GateWay 第四章 Spring Cloud Alibaba 整合Sentinel 第五章 Spring Cloud Alibaba 整合SkyWalking链路跟踪 第六章 Spring Cloud Alibaba 整合Seata分布式事务 第七章 Spring Cloud 集成Auth用…

Jenkins通知目标服务器拉取Harbor镜像部署

1.告诉目标服务器拉取哪个镜像 2.判断当前有没有正在运行此容器&#xff0c;有就删除 3.接着查看拉取的镜像目标服务器上是否已存在&#xff0c;有就删除 4.拉取Harbor镜像 5.运行容器 目标服务器编写脚本 创建个部署脚本 vim deploy.sh告诉目标服务器Harbor地址、仓库、镜像…

零基础学python:5、循环语句的使用

循环 目标 程序的三大流程while 循环基本使用break 和 continuewhile 循环嵌套01. 程序的三大流程 在程序开发中,一共有三种流程方式: 顺序 —— 从上向下,顺序执行代码分支 —— 根据条件判断,决定执行代码的 分支循环 —— 让 特定代码 重复 执行02. while 循环基本使用…

基于卷积神经网络实现手写数字识别

基于卷积神经网络实现手写数字识别 基于卷积神经网络实现手写数字识别。具体过程如下&#xff1a; &#xff08;1&#xff09; 定义ConvNet结构类及其前向传播方式 &#xff08;2&#xff09; 设置超参数以及导入相关的包。 &#xff08;3&#xff09; 定义训练网络函数和绘…

R语言实现多要素偏相关分析

偏相关分析是指当两个变量同时与第三个变量相关时&#xff0c;将第三个变量的影响剔除&#xff0c;只分析另外两个变量之间相关程度的过程&#xff0c;判定指标是相关系数的R值。 在GIS中&#xff0c;偏相关分析也十分常见&#xff0c;我们经常需要分析某一个指数与相关环境参…

基于Java中的SSM框架实现快餐店线上点餐系统项目【项目源码+论文说明】

基于Java中的SSM框架实现快餐店线上点餐系统演示 摘要 随着计算机互联网的高速发展。餐饮业的发展也加入了电子商务团队。各种网上点餐系统纷纷涌现&#xff0c;不仅增加了商户的销售量和营业额&#xff0c;而且为买家提供了极大的方便&#xff0c;足不出户&#xff0c;就能订…

Docker进阶教程 - 4 Docker网络

更好的阅读体验&#xff1a;点这里 &#xff08; www.doubibiji.com &#xff09; 4 Docker网络 先说我们现在遇到的问题&#xff1a; 我们现在有一个 Redis 容器&#xff0c;一个 SpringBoot 项目容器&#xff0c;在 SpringBoot 项目的代码中如何访问 Redis 容器中的服务呢…

Harbor镜像仓库的安装和使用

1 Harbor安装 参考文章&#xff1a; 银河麒麟v10离线安装harbor 由于配置了本地私有yum源&#xff0c;因此&#xff0c;直接使用yum命令安装docker和docker-compose 1.1 安装docker yum install docker-ce1.2 安装docker-compose yum install docker-compose1.3 安装harbo…