深入解析与解决高并发下的线程池死锁问题

问题背景

在现代互联网应用中,高并发场景是常态,为了高效处理大量用户请求,后端服务通常会采用线程池来管理线程资源。然而,在一个复杂的微服务架构项目中,我们遇到了一个棘手的问题:在业务高峰期,系统频繁出现响应延迟甚至超时的情况,经过初步排查,发现部分服务存在线程池死锁现象,严重影响了系统的稳定性和用户体验。

问题分析

该系统采用Spring Boot框架构建,核心业务模块负责处理用户订单,包括订单创建、支付状态更新以及库存调整等操作。为了提高处理效率,我们为每个处理逻辑配置了独立的线程池。问题主要出现在订单支付成功后的库存减少操作上,具体代码片段如下:

@Service
public class OrderService {@Autowiredprivate StockService stockService;@Async("stockThreadPool")public void deductStockAfterPaymentSuccess(Order order) {stockService.deduct(order.getProductId(), order.getQuantity());// 更新订单状态等后续逻辑...}
}

其中,stockThreadPool 是一个自定义配置的线程池,用于处理库存相关的异步操作,以避免库存操作阻塞主线程。然而,在高并发环境下,该线程池经常达到最大线程数,新来的请求因无法获取线程而被无限期地等待,导致线程池死锁。

排查过程
  1. 监控工具辅助:首先,利用JVisualVM、arthas等工具监控系统线程状态,发现stockThreadPool中的线程大多处于WAITING状态,说明存在线程等待资源释放的情况。
  2. 代码审查:检查StockService.deduct()方法实现,发现内部使用了悲观锁(如synchronized或Lock的lock()方法),在高并发下容易形成锁竞争,导致线程等待时间过长。
  3. 日志分析:通过增加详细的日志记录,观察到在某些时间点,多个线程同时尝试锁定相同的商品库存记录,形成了锁链,进而引发了死锁。
解决方案
  1. 优化锁策略:将悲观锁改为乐观锁。在库存服务中,使用版本号或时间戳进行并发控制,减少直接的线程等待。例如,使用@Version注解结合Spring Data JPA的乐观锁机制。
@Entity
public class Product {@Idprivate Long id;private Integer stock;@Versionprivate Long version;// getters and setters
}
@Service
public class StockService {@Transactionalpublic boolean deduct(Long productId, Integer quantity) {Product product = productRepository.findById(productId).orElseThrow(() -> new ResourceNotFoundException("Product not found"));if (product.getStock() < quantity) return false;int updatedStock = product.getStock() - quantity;product.setStock(updatedStock);try {product = productRepository.save(product);} catch (OptimisticLockException e) {// 处理乐观锁失败,通常重试或记录日志return false;}return true;}
}

线程池配置优化:根据业务负载情况动态调整线程池参数,如核心线程数、最大线程数、队列大小及拒绝策略等,避免固定配置在高并发下成为瓶颈。使用ThreadPoolExecutor自定义配置,并考虑使用ThreadPoolExecutor.CallerRunsPolicy作为拒绝策略,让调用者线程执行任务,避免直接丢弃任务。

@Configuration
public class ThreadPoolConfig {@Bean(name = "optimizedThreadPool")public ThreadPoolTaskExecutor threadPoolTaskExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();// 核心线程数,根据CPU核心数和业务需求合理设定executor.setCorePoolSize(4 * Runtime.getRuntime().availableProcessors());// 最大线程数,防止线程数无限制增长导致资源耗尽executor.setMaxPoolSize(executor.getCorePoolSize() * 2);// 队列大小,当核心线程都被占用时,新任务将在队列中等待executor.setQueueCapacity(500);// 拒绝策略,这里采用CallerRunsPolicy,让调用者线程执行任务executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());// 设置线程空闲时间,超过该时间的空闲线程将被终止executor.setKeepAliveSeconds(60);// 线程名前缀,方便日志追踪executor.setThreadNamePrefix("OptimizedThreadPool-");// 初始化线程池executor.initialize();return executor;}
}

在服务类中使用自定义线程池:

@Service
public class OrderService {@Autowired@Qualifier("optimizedThreadPool") // 使用自定义线程池private ThreadPoolTaskExecutor executor;public void deductStockAfterPaymentSuccess(Order order) {executor.execute(() -> {stockService.deduct(order.getProductId(), order.getQuantity());// 更新订单状态等后续逻辑...});}
}

增加超时与重试机制:对于可能引起长时间等待的操作,如数据库操作、远程服务调用等,设置合理的超时时间,并在超时后实施重试逻辑,以减少单次请求对系统资源的占用, 使用Future结合自定义的超时和重试。

@Service
public class OrderService {// ... 其他代码 ...public void deductStockWithRetry(Order order) {int retryCount = 0;final int maxRetries = 3; // 最大重试次数while (retryCount <= maxRetries) {try {Future<Boolean> resultFuture = executor.submit(() -> stockService.deduct(order.getProductId(), order.getQuantity()));// 使用自定义的超时时间,5秒if (resultFuture.get(5, TimeUnit.SECONDS)) {System.out.println("库存扣减成功");break; // 成功则跳出循环} else {throw new RuntimeException("库存不足,扣减失败");}} catch (TimeoutException e) {System.out.println("库存扣减操作超时,准备重试...");} catch (InterruptedException | ExecutionException e) {System.out.println("操作异常,准备重试... " + e.getMessage());}retryCount++;if (retryCount > maxRetries) {System.out.println("重试次数已达上限,操作失败");break;} else {try {Thread.sleep(1000); // 退避策略,重试前等待一秒} catch (InterruptedException ignored) {}}}}
}
成果与思考

经过上述改造,系统在高并发场景下的表现有了显著提升,线程池死锁问题得到有效解决,用户请求响应时间和系统稳定性得到了大幅改善。监控数据显示,线程池利用率更加合理,未再出现请求堆积和长时间等待的情况。

此次经历让我们深刻认识到:

  • 并发控制策略的重要性:合理选择锁策略(乐观锁与悲观锁),能够有效避免死锁和性能瓶颈。
  • 线程池配置的灵活性:动态调整线程池参数,根据实际业务需求和系统负载进行优化,是保障系统稳定的关键。
  • 全面的监控与日志:良好的监控和日志体系是问题定位和系统调优的基石,能够快速定位并解决问题。

总之,面对高并发挑战,开发人员需要综合运用多种技术手段,不断优化和调整,才能确保系统的高效稳定运行。

欢迎扫码关注 微信公众号:JAVA和人工智能
                                                           获取更多免费书籍、资源、优质资料 

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

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

相关文章

WSL安装的Ubuntu与docker desktop集成

WSL安装的Ubuntu与docker desktop集成 最近因为项目需要&#xff0c;要在本地利用WSL搭建一个docker和Ubuntu的部署环境。一开始并不知道docker desktop与Ubuntu可以集成使用&#xff0c;所以在Ubuntu上独立安装了docker引擎&#xff0c;但在安装docker-compose的时候出现以下…

MN316 AT模式丨低功耗实测分析

NB-IoT模组的应用场景一般具备低频次、小数据量、上行为主、工作时间短&#xff08;激活态时间短&#xff09;等特点。因此&#xff0c;休眠态的功耗是NB-IoT模组产品综合耗电的重点考量参数之一。中移物联OneMO超低功耗NB-IoT模组MN316&#xff0c;凭借其紧凑的尺寸、极低的休…

天翼云服务器80、443等特殊端口无法访问原因记录

之前阿里云、腾讯云的服务器上&#xff0c;想要用域名访问项目简单配置就好了&#xff0c;这次甲方直接买的翼云的服务器&#xff0c;配置了半天&#xff0c;防火墙端口80、443端口开放了&#xff0c;控制台安全组也添加了&#xff0c;就是不能用域名或IP直接访问&#xff0c;配…

51串口通讯

介绍 串口是一种应用十分广泛的通讯接口&#xff0c;串口成本低、容易使用、通信线路简单&#xff0c;可实现两个设备的互相通信。单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信&#xff0c;极大的扩展了单片机的应用范围&#xff0…

Java读取指定 JAR 包路径中的 git.properties 文件

Java读取指定 JAR 包路径中的 git.properties 文件 在上述代码中&#xff0c;首先打开 JAR 文件&#xff0c;获取 git.properties 文件的 JarEntry 对象&#xff0c;如果存在该条目&#xff0c;就获取其输入流进行后续的读取和处理。具体的读取和处理逻辑需要根据您的实际需求在…

淘宝item_password接口技术详解

淘宝item_password接口技术详解 一、引言 在电商领域&#xff0c;淘宝作为中国最大的在线购物平台之一&#xff0c;拥有海量的商品信息和用户数据。为了方便用户快速访问和分享商品&#xff0c;淘宝推出了淘口令功能&#xff0c;这是一种加密的链接形式&#xff0c;用户可以在…

在QLineEdit或QPushButton上检测Enter键按下

前言 在开发一个游戏应用时,玩家需要猜测系统随机选择的数字。当玩家输入一个数字并点击“Play”按钮后,应用会根据玩家的猜测给出反馈。然而,频繁地点击按钮显得有些不便。为了提升用户体验,我们希望在玩家按下Enter键时也能触发相同的操作。本文将介绍如何在QLineEdit和…

一文搞懂Python局部变量与全局变量的12大陷阱

今天我们要来聊聊一个让人又爱又恨的话题——局部变量与全局变量的八大迷雾。在Python的世界里&#xff0c;变量就像是你的小宠物&#xff0c;有时候它们乖乖听话&#xff0c;但一不小心就给你挖了个大坑&#xff01;别担心&#xff0c;今天我们就一起把这些陷阱挖出来&#xf…

基于VTK9.3.0+Visual Studio2017 c++实现DICOM影像MPR多平面重建+V R体绘制4个视图展示功能的实现

开源库&#xff1a;VTK9.3.0 开发工具&#xff1a;Visual Studio2017 开发语言&#xff1a;C 实现过程&#xff1a; class vtkImageInteractionCallback : public vtkCommand { public:static vtkImageInteractionCallback* New(){return new vtkImageInteractionCallback()…

文库小程序搭建部署:实现资源共享正向反馈

文档库相信大家应该不陌生&#xff0c;日常我们的工作模板、会议模板、求职时的简历模板、教育界的教学模板等来源方式都出自于文档库&#xff0c;随着互联网的发展和工作需求&#xff0c;文档模板开启了新型的知识变现新途径&#xff0c;通过文库小程序&#xff0c;我们不仅能…

Web服务器与Apache(LAMP架构+搭建论坛)

一、Web基础 1.HTML概述 HTML&#xff08;Hypertext Markup Language&#xff09;是一种标记语音,用于创建和组织Web页面的结构和内容&#xff0c;HTML是构建Web页面的基础&#xff0c;定义了页面的结构和内容&#xff0c;通过标记和元素来实现 2.HTML文件结构 <html>…

压缩pdf文件大小在线,在线免费压缩pdf

在现在办公中&#xff0c;PDF文档已经成为我们日常工作中不可或缺的一部分。然而&#xff0c;随着文档内容的不断丰富&#xff0c;PDF文件的大小也逐渐增大&#xff0c;这不仅占用了大量的存储空间&#xff0c;而且在传输和共享时也显得尤为不便。所以有时候我们需要把pdf压缩小…

文本三剑客之awk

awk 按行取列 awk默认的分割符&#xff1a;空格&#xff0c;tab键&#xff0c;多个空格自动压缩成一个 awk的工作原理&#xff0c;根据指令信息&#xff0c;逐行读取文本内容&#xff0c;然后按照条件进行1格式化输出 awk的选项&#xff1a; -F 指定分割符&#xff0c;默认就是…

Git回滚到某次提交

要在Git中回滚到某次提交&#xff0c;你可以使用git reset命令。但是&#xff0c;你需要明确你想要进行的是“软回滚”&#xff08;soft reset&#xff09;&#xff0c;“混合回滚”&#xff08;mixed reset&#xff09;还是“硬回滚”&#xff08;hard reset&#xff09;&…

# Kafka_深入探秘者(4):kafka 主题 topic

Kafka_深入探秘者&#xff08;4&#xff09;&#xff1a;kafka 主题 topic 一、kafka 主题管理 1、kafka 创建主题 topic 命令 1&#xff09;命令&#xff1a; # 切换到 kafka 安装目录 cd /usr/local/kafka/kafka_2.12-2.8.0/# 创建一个名为 heima 的 主题 bin/kafka-topic…

揭秘!速卖通卖家如何靠自养号测评打造爆款?

然而&#xff0c;许多商家对测评的认识存在严重的误区&#xff0c;他们错误地以为仅仅通过几次草率的测评就能快速塑造出爆款产品。实际上&#xff0c;测评远非如此简单&#xff0c;它是一个需要深思熟虑、精心策划和持续投入的过程。测评的真正价值在于帮助平台精准地把握产品…

【C语言课程设计】员工信息管理系统

员工信息管理系统 在日常的企业管理中&#xff0c;员工信息的管理显得尤为重要。为了提高员工信息管理的效率&#xff0c;我们设计并实现了一个简单的员工信息管理系统。该系统主要使用C语言编写&#xff0c;具备输入、显示、查询、更新&#xff08;增加、删除、修改&#xff…

数字排列问题

题目&#xff1a;有1、2、3、4个数字&#xff0c;能组成多少个互不相同且无重复数字的三位数&#xff1f;都是多少&#xff1f; 代码&#xff1a; #include <stdio.h> int main() { int count 0; // 计数器&#xff0c;记录生成的三位数的数量 // 使用三个嵌套的fo…

代数扩张次数关系定理

【单扩域同构引理】 对于单扩张 K / F \mathbb{K/F} K/F有同构 F [ a ] ≅ F [ x ] / ⟨ f ( x ) ⟩ \mathbb{F}\lbrack a\rbrack\mathbb{\cong F}\lbrack x\rbrack/\left\langle f(x) \right\rangle F[a]≅F[x]/⟨f(x)⟩&#xff0c;其中 a ∈ K a \in \mathbb{K} a∈K为本原元…

Java-LinkedList和ArrayList的区别、Get/Add操作性能分析以及常见的遍历方式

LinkedList和ArrayList的区别、Get/Add操作性能分析以及常见的遍历方式 一、LinkedList基本特性主要方法 二、ArrayList初始化及基本操作ArrayList注意点&#xff08;待完善&#xff09;代码示例 三、ArrayList与LinkedList的区别四、Get/Add操作性能分析五、LinkedList遍历方式…