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: 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的代码风格函数式、简洁、优雅,也使得代码更加易读易维护。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

现代卷积神经网络

深度卷积神经网络&#xff08;AlexNet&#xff09; 经典机器学习的流水线&#xff1a; ①获取一个有趣的数据集&#xff1b; ②根据光学、几何学&#xff0c;手动对特征数据集进行预处理&#xff1b; ③通过标准的特征提取算法&#xff0c;如SIFT&#xff08;尺度不变特征变…

BT 宝塔面板 宝塔面板可以登录, 但是使用里边功能的时候就被拒绝链接

目录 问题 面板可以登录 功能请求被拒绝尝试重启面板 也不行解决方案 更新BT版本 问题 面板可以登录 功能请求被拒绝 面板可以登录 但是 使用里边功能的时候 就会被拒绝 尝试重启面板 也不行 气的我都想重装了 但是我又懒得配置 我怀疑是请求头的问题 解决方案 更新BT版本 …

【力扣hot100】128.最长连续序列

给定一个未排序的整数数组 nums &#xff0c;找出数字连续的最长序列&#xff08;不要求序列元素在原数组中连续&#xff09;的长度。 请你设计并实现时间复杂度为 O(n) 的算法解决此问题。 示例 1&#xff1a; 输入&#xff1a;nums [100,4,200,1,3,2] 输出&#xff1a;4 解…

【运维】MacOS Wifi热点设置

目录 打开热点 配置共享网段 打开热点 打开macOS设置&#xff0c;进入通用->共享 点击如下图标进行配置&#xff0c; 会进入如下界面&#xff08;⚠️目前是打开共享状态&#xff0c;无法修改配置&#xff0c;只有在未打开状态才能进入配置&#xff09; 配置完成后&#x…

WebClient上载文件——实现将本地文件同步到远端服务器上

问题描述 用户上传产品示例图片到服务器端上&#xff0c;客户端在请求图片资源时&#xff0c;当服务端架设了多个节点的情况下&#xff0c;由于没有负载均衡请求到保存图片资源的服务器&#xff0c;出现图片访问404的问题。 这里保存上传文件时&#xff0c;同时需要将该文件保…

PTA题解 --- 阶梯电价(C语言)

今天是PTA题库解法讲解的第五天&#xff0c;今天我们要讲解A-B&#xff0c;题目如下&#xff1a; 解题思路&#xff1a; 要解决这个问题&#xff0c;我们可以编写一个C语言程序&#xff0c;首先判断输入的月用电量是否有效&#xff08;即大于等于0&#xff09;。如果有效&…

java设计模式(1)---总则

设计模式总则 一、概述 1、什么是设计模式 设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。 解释下&#xff1a; 分类编目&#xff1a;就是说可以找到一些特征去划分这些设计模式&#xff0c;从而进行分类。 代码设计经验&#xff1a;这句很重…

大屏可视化综合展示解决方案

1.系统概述 1.1.需求分析 1.2.重难点分析 1.3.重难点解决措施 2.系统架构设计 2.1.系统架构图 2.2.关键技术 2.3.接口及要求 3.系统功能设计 3.1.功能清单列表 3.2.数据源管理 3.3.数据集管理 3.4.视图管理 3.5.仪表盘管理 3.6.移动端设计 3.1.系统权限设计 3.…

HAL STM32G4 +TIM1 3路PWM互补输出+VOFA波形演示

HAL STM32G4 TIM1 3路PWM互补输出VOFA波形演示 ✨最近学习研究无刷电机驱动&#xff0c;虽然之前有使用过&#xff0c;但是在STM32上还没实现过。本文内容参考欧拉电子例程&#xff0c;从PWM驱动开始学习。 欧拉电子相关视频讲解&#xff1a; STM32G4 FOC开发实战—高级定时器发…

一步到位:用Python实现PC屏幕截图并自动发送邮件,实现屏幕监控

在当前的数字化世界中&#xff0c;自动化已经成为我们日常生活和工作中的关键部分。它不仅提高了效率&#xff0c;还节省了大量的时间和精力。在这篇文章中&#xff0c;我们将探讨如何使用Python来实现一个特定的自动化任务 - PC屏幕截图自动发送到指定的邮箱。 这个任务可能看…

抢滩中东商机!2024年现在入局是否还为时不晚?

有人说&#xff0c;在2024年&#xff0c;做外贸工厂跨境电商的老板&#xff0c;想要翻身&#xff0c;甚至想改变命运&#xff0c;有两个路径&#xff0c;其中一个路径就是&#xff1a;做沙特电商。 入局中东电商好时机 经商环境好&#xff1a;欧美市场竞争激烈&#xff0c;进入…

第十四届蓝桥杯大赛软件赛省赛Java大学B组

最近正在备考蓝桥杯&#xff0c;报的java b组&#xff0c;顺便更一下蓝桥的 幸运数字 题目 思路&#xff1a;填空题&#xff0c;暴力即可 import java.util.Scanner; // 1:无需package // 2: 类名必须Main, 不可修改public class Main {static int trans(int x, int y){int …

【目标检测基础篇】目标检测评价指标:mAP计算的超详细举例分析以及coco数据集标准详解(AP/AP50/APsmall.....))

学习视频&#xff1a; 霹雳吧啦Wz-目标检测mAP计算以及coco评价标准 【目标检测】指标介绍&#xff1a;mAP 1 TP/FP/FN TP(True Positive) : IoU>0.5的检测框数量(同一Ground truth只计算一次)FP(False Positive) : IoU<0.5的检测框(或者是检测到同一个GT的多余检测框的…

nacos 更新报错“发布失败。请检查参数是否正确”

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容起因解决方案结果 &#x1f4e2;文章总结&#x1f4e5;博主目标 &#x1f50a;博主介绍 &#x1f31f;我是廖志伟&#xff0c;一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专家博主、阿里云专家博主、清华…

FL Studio2024全能数字编曲音频工作站,打造专业电音的不二之选!

FL Studio2024全能数字编曲音频工作站&#xff0c;打造专业电音的不二之选&#xff01; 专业机构力荐&#xff0c;让你的音乐创作如虎添翼 在音乐的世界里&#xff0c;没有什么比创作出属于自己的独特旋律更令人兴奋的了。 而今天&#xff0c;我们为你带来了一款能够让音乐制作…

Hive SQL必刷练习题:排列组合问题【通过join不等式】

排列组合问题【通过join不等式】 这种问题&#xff0c;就是数学的排列不等式&#xff0c;一个队伍只能和其余队伍比一次&#xff0c;不能重复 方法1&#xff1a;可以直接通过join&#xff0c;最后on是一个不等式【排列组合问题的解决方式】 方法2&#xff1a;也可以是提前多加…

PDF文件如何以数字进行批量重命名?以数字重命名的PDF文件

在日常生活和工作中&#xff0c;我们经常需要处理大量的PDF文件&#xff0c;如文档、报告、合同等。为了更高效地管理这些文件&#xff0c;一个有效的方式就是对它们进行批量命名。批量命名不仅能提高文件的组织性&#xff0c;还能节省大量时间。下面&#xff0c;我们将详细介绍…

springboot实现文件上传

SpringBoot默认静态资源访问方式 首先想到的就是可以通过SpringBoot通常访问静态资源的方式&#xff0c;当访问&#xff1a;项目根路径 / 静态文件名时&#xff0c;SpringBoot会依次去类路径下的四个静态资源目录下查找&#xff08;默认配置&#xff09;。 在资源文件resour…

关于调度算法,小林给出更好的例子(银行办理业务)

看的迷迷糊糊&#xff1f;那我拿去银行办业务的例子&#xff0c;把上面的调度算法串起来&#xff0c;你还不懂&#xff0c;你锤我&#xff01; 办理业务的客户相当于进程&#xff0c;银行窗口工作人员相当于 CPU。 现在&#xff0c;假设这个银行只有一个窗口&#xff08;单核 …

个人家庭安装光伏流程及注意事项

今年《政府工作报告》提出&#xff0c;推动分布式能源开发利用。这是“分布式能源”首次被写入《政府工作报告》。从地方层面来看&#xff0c;“分布式能源”也被多个地方列入今年的政府工作重点&#xff0c;可见光伏发展依旧强势。 光伏安装流程大概分为以下几步&#xff1a; …