线程池本身不会死锁,但其中的工作线程可能因代码逻辑发生java级死锁,表现为活跃线程满载、cpu偏低、任务堆积且无响应;定位关键是用jstack -l 或threadmxbean.finddeadlockedthreads()识别循环等待的阻塞线程。

线程池本身不会“死锁”,但线程池中的线程可能因代码逻辑陷入死锁,导致任务持续堆积、线程全部阻塞、服务无响应。定位的关键不是查“线程池死锁”,而是查“线程池中哪些工作线程发生了 Java 级别死锁”,并确认其是否由线程池调度触发。
看现象:先确认是不是真死锁
别一上来就 dump,先观察典型特征:
- 线程池活跃线程数长期满载(如 200/200),但 CPU 使用率偏低(
- HTTP 接口大量超时(尤其是健康检查 /actuator/health 响应卡顿或失败)
- 日志停止打印新内容,或只重复输出相同 warn/error(无新业务日志)
- 线程堆栈中大量出现 WAITING 或 BLOCKED 状态,且长时间不变
抓快照:用 jstack -l 获取带锁信息的线程转储
必须加 -l 参数,否则 ReentrantLock 等显式锁信息会丢失:
- 用启动应用的同一用户执行:
jstack -l <pid> > thread_dump.log</pid> - 生产环境建议加时间戳保存:
jstack -l <pid> > jstack_$(date +%Y%m%d_%H%M%S).log</pid> - 重点搜索关键词:Found one Java-level deadlock —— 如果存在,后面会直接列出互相等待的线程和锁地址
- 若没看到该提示,不代表没死锁;可能是跨层(如 DB 行锁 + 应用锁)、StampedLock、或自定义同步器,jstack 无法识别
读堆栈:从 locked 和 waiting to lock 定位循环链
找到疑似阻塞线程后,逐行对照两个关键字段:
- locked :该线程当前持有的锁对象地址
- waiting to lock :它正在等待的另一个锁地址
- 顺着地址找另一处:看哪个线程在 locked 那个地址,同时又在 waiting to lock 前者持有的地址
- 结合 stack trace 中的类名、方法名、行号,快速定位到业务代码中加锁位置(例如 UserService.updateOrder() 第 42 行)
补验证:用 ThreadMXBean 主动检测(适合嵌入监控)
比人工 jstack 更可控,可集成进健康检查或定时巡检:
- 调用
ManagementFactory.getThreadMXBean().findDeadlockedThreads() - 返回非 null 数组即表示 JVM 检测到 synchronized 或 java.util.concurrent.Lock 的死锁
- 配合
getThreadInfo(ids, true, true)获取完整栈和锁详情(两个 true 缺一不可) - 注意:它不覆盖数据库锁、native 锁、Condition.await() 场景,结果为 null ≠ 绝对安全
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











