父子任务共用一个全局线程池导致死锁

父子任务共用一个全局线程池导致死锁

一.背景

目前所有项目已升级至JDK 21,并采用了虚拟线程的新特性。然而,在synchronized方法块或类加载等隐式持有锁的场景中,虚拟线程可能导致平台线程被绑定,从而引发死锁问题。这种情况在集团框架中偶尔出现,导致应用程序卡死,影响业务连续性。为了解决这一问题,我们决定使用平台线程池执行核心业务。

二.问题

2.1 表象

由Qshedule调度的Job执行时间过长,导致系统出现不推单情况,线程池数量始终维持在核心线程数。

2.2 提出疑问

为什么使用虚拟线程的时候没有问题 ? 使用平台线程执行任务的时候反而出现了问题 ?

2.2 问题定位

  • 堆栈信息

查看多个堆栈信息发现,所有的线程全部卡在了pullAndHandleRouteLineSortedSet方法

  • 业务逻辑

查看业务代码发现,父任务会创建多个子任务,并等待子任务执行结束,而父子任务都是使用的同一个线程池,当线程池中执行线程都是父任务时,所有的子任务又都在任务队列中等待执行,所以这样就会发生死锁。

核心线程永远不会释放,而任务队列的大小又足够大,导致不会扩容线程。所以就出现了死锁。

  • 疑问:同样的代码为什么使用虚拟线程没问题,而使用平台线程池有问题?

    使用虚拟线程执行阻塞任务时,载体线程会yeild让出,而直接使用平台线程执行任务时,遇到阻塞时并不会让出线程从而导致上述问题。

三.解决方案

为避免父子任务使用同一线程池造成死锁,可以考虑使用独立线程池:将父任务和子任务分别提交到不同的线程池,避免共享线程池资源,减少死锁的可能性。

四.总结

  • 禁止使用Executors创建自定义线程池。使用ThreadPoolExecutor创建线程池时,注意每个参数的含义,规避资源耗尽的风险。
  • 线程池使用有界队列,避免使用无界队列。
  • 对于父子任务的场景,可以使用线程池或者 MQ。使用有界队列之后,制定合理的拒绝策略,拒绝策略可以考虑 MQ 做重试。
    以使用线程池或者 MQ。使用有界队列之后,制定合理的拒绝策略,拒绝策略可以考虑 MQ 做重试。
  • 不同业务使用不同的线程池,禁止父子任务使用相同的线程池。
相关推荐
小杍随笔4 分钟前
Rust桌面GUI框架:性能优化与实战避坑指南
开发语言·性能优化·rust
二哈赛车手7 分钟前
新人笔记---项目中简易版的RAG检索后评测指标(@Recall ,Mrr..)实现
java·开发语言·笔记·spring·ai
做时间的朋友。8 分钟前
精准核酸检测
java·数据结构·算法
格林威9 分钟前
3D相机视觉检测:环境光太强,结构光点云全是噪点怎么办?
开发语言·人工智能·数码相机·计算机视觉·3d·视觉检测·工业相机
Rust语言中文社区16 分钟前
【Rust日报】2026-05-02 Temper - 用 Rust 编写的 Minecraft 服务器项目发布 0.1.0 版
运维·服务器·开发语言·后端·rust
许彰午21 分钟前
CacheSQL(五):桥接篇
java·数据库·缓存·系统架构
ATCH IERV32 分钟前
Java实战:Spring Boot application.yml配置文件详解
java·网络·spring boot
咸鱼2.01 小时前
【java入门到放弃】XXL-JOB
java
爱滑雪的码农1 小时前
Java基础十一 流(Stream)、文件(File)和IO
java·开发语言·python
叶小鸡1 小时前
Java 篇-项目实战-天机学堂(从0到1)-day11
java·开发语言