复盘死锁需从线程行为、资源依赖、系统表现三层深入,聚焦WAITING/BLOCKED线程的锁地址闭环,对照代码确认锁序一致性,补运行时防护(超时、监控、日志埋点),并固化为Code Review规则与设计规范。

复盘死锁不能只看“哪段代码卡住了”,得顺着线程行为、资源依赖和系统表现三层往下挖。重点不是还原现场,而是把偶然暴露的问题,转化成可落地的防御动作。
从 jstack 输出反推执行路径
拿到线程堆栈后,别急着抄代码改顺序。先盯住两件事:
- 找出所有状态为 WAITING (on object monitor) 或 BLOCKED (on object monitor) 的线程,它们就是死锁链上的节点
- 逐行看每个线程的 waiting to lock 和 locked 地址是否形成闭环——比如线程A在等0x123,而线程B正持有0x123并等待0x456,线程A又恰好持有0x456
这个闭环就是真实发生的锁序冲突点,比代码里写的“理论上可能”更有说服力。
对照代码确认锁获取顺序是否一致
找到闭环后,回到源码,逐方法检查:
- 每个 synchronized 块或 lock.lock() 调用,对应的是哪个对象?有没有隐式锁(如 this、Class 对象)混入?
- 多个锁是否总按同一顺序申请?比如方法 A 先 lock1 再 lock2,方法 B 却先 lock2 再 lock1,这就是典型隐患
- 注意工具类、框架回调(如 Spring AOP、Dubbo Filter)是否悄悄加了额外锁,容易被忽略
补上运行时防护和监控断点
光靠人盯代码防不住变化。线上必须有自动兜底:
- 在关键业务线程池初始化时,开启 JVM 参数 -XX:+PrintConcurrentLocks 或定期调用 java.lang.management.ThreadMXBean.findDeadlockedThreads() 主动探测
- 对高频、长耗时的锁操作,加上超时控制:用 ReentrantLock.tryLock(timeout, unit) 替代无条件 synchronized,失败时记录告警并降级
- 在日志中统一埋点:每次进入锁前打一条 acquire lock [xxx] on [obj],释放时打 release lock [xxx],出问题时能快速串起锁生命周期
把复盘结论变成团队可执行的约束
最后一步最关键:把这次教训固化成开发流程里的硬规则。
- 在 Code Review CheckList 里加一条:“涉及多把锁的方法,必须标注锁获取顺序,并确保所有调用路径一致”
- 用 SpotBugs 或自定义 Sonar 规则扫描 synchronized(obj1) { synchronized(obj2) { ... } } 这类嵌套,强制要求加注释说明顺序依据
- 新模块设计文档中,锁资源要像数据库表一样登记:名称、作用、持有者、最长持有时间、是否可重入——让锁成为显性资产,而不是隐藏副作用
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











