java中synchronized死锁需靠jstack/jconsole/threadmxbean主动检测,预防则通过统一加锁顺序、缩小同步块、避免锁内调用外部方法,并可用reentrantlock超时、concurrenthashmap等替代方案。

Java中用 synchronized 造成的死锁无法自动解除,也不能靠JVM强制中断,只能靠提前预防和及时排查。关键不是“等它发生再救”,而是从设计和工具两头控制:一边用规范避免循环等待,一边用工具快速定位卡点。
怎么快速发现synchronized死锁?
不用等线上报警,本地或测试环境就能抓到——核心是看线程是否在互相等待同一组锁。
-
jstack 是首选工具:先用
jps找到进程ID,再执行jstack <pid></pid>,输出里直接搜索Found 1 deadlock。如果有,下面会清晰列出哪几个线程、各自持有什么锁、正在等哪个锁,连锁对象的十六进制地址都标好了。 -
jconsole 图形化确认:启动
jconsole→ 连上目标进程 → 切到“线程”页 → 点“检测死锁”按钮。一旦发现,会弹出新标签页,用箭头图展示线程间的等待链,比纯文本更直观。 -
程序内主动检查:用
ThreadMXBean.findDeadlockedThreads()写个健康检查接口,定时调用。返回非空数组就说明已发生死锁,可立即告警或记录日志。
怎么从代码层面预防synchronized死锁?
破坏死锁四条件中“持有并等待”和“循环等待”最可行,互斥和不可剥夺是锁机制本身决定的,不建议动。
-
统一加锁顺序:所有线程按相同规则获取多个锁。比如按锁对象的
System.identityHashCode()排序,谁小谁先锁;或者约定业务层始终先锁用户ID、再锁订单ID。只要顺序一致,就不会出现A等B、B等A的闭环。 -
缩小同步块范围:只在真正需要保护的临界区用
synchronized,避免在锁内做耗时操作(如IO、远程调用、sleep)。锁得越久,其他线程等待时间越长,死锁概率越高。 - 避免锁内调用外部方法:尤其不能调用可能也加锁的方法(比如另一个服务的同步方法),否则等于把未知锁引入当前锁链,极易形成隐式循环依赖。
有没有更安全的替代方案?
不是所有场景都必须用 synchronized,有些问题换种思路就能绕开死锁风险。
-
用
tryLock配超时:改用ReentrantLock的tryLock(long, TimeUnit),设置合理超时(如200ms)。失败就释放已持锁、回退重试或降级处理,不卡死。 -
减少锁粒度或换无锁结构:比如用
ConcurrentHashMap替代HashMap + synchronized;对计数类场景,优先用LongAdder而不是synchronized ++。 -
用不可变对象和线程局部变量:如果数据能做成不可变(
final字段+无setter),天然无需加锁;部分状态可用ThreadLocal隔离,彻底避开共享竞争。
死锁不是玄学,它有明确的触发路径和可验证的破局点。排查靠工具直击现场,预防靠设计守住边界——两者结合,synchronized 就不会变成定时炸弹。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











