死锁是线程因资源争抢陷入永久互相等待的确定性结果,需同时满足互斥、持有并等待、不可剥夺、循环等待四条件;破坏任一条件即可避免。

Java 多线程死锁本质是线程间因资源争抢陷入永久互相等待,不是偶发异常,而是四个必要条件同时满足时的确定性结果。只要破坏其中任意一个,死锁就不会发生——这是排查和预防的底层逻辑。
死锁产生的四大必要条件(必须同时满足)
理解这四点,才能精准定位问题根源,而非盲目加日志或调参:
- 互斥条件:资源不可共享,如 synchronized 锁、ReentrantLock,默认排他。这是并发安全的前提,一般不破坏。
- 持有并等待:线程已持有一个锁,又去申请另一个锁,且不释放已有锁。这是最常被干预的环节。
- 不可剥夺:锁不能被系统强制回收,只能由持有线程主动释放。synchronized 和默认 ReentrantLock 都满足此特性。
- 循环等待:线程 A 等 B 的锁,B 等 C 的锁,C 又等 A 的锁,形成闭环。这是死锁最直观的表象,也是最容易通过代码结构规避的。
典型死锁场景与复现代码
以下两种模式在真实项目中高频出现,可直接复现验证:
-
锁顺序不一致:两个线程以相反顺序获取同一组锁。
例如线程1先 lockA 后 lockB,线程2先 lockB 后 lockA;一旦时序凑巧(如 sleep 延迟),立刻卡死。 -
动态参数导致的隐式顺序颠倒:常见于转账、库存扣减等业务。
比如 transfer(Account from, Account to),若线程A调用 transfer(acc1, acc2),线程B调用 transfer(acc2, acc1),内部若按参数顺序加锁,就等同于上一种情况。
快速定位死锁的实战方法
线上服务“卡住”时,优先执行以下三步,5 分钟内确认是否为死锁:
- 用 jps 查出 Java 进程 PID;
- 执行 jstack -l
,搜索输出中的 “Found one Java-level deadlock”; - 查看紧随其后的线程堆栈,明确哪几个线程、持有哪些锁、正在等待哪些锁——信息足够直接定位到代码行。
开发阶段也可用 ThreadMXBean 编程检测:ManagementFactory.getThreadMXBean().findDeadlockedThreads() 返回非 null 数组即存在死锁。
有效预防死锁的落地策略
不是靠运气避免,而是从设计层面切断死锁路径:
- 统一锁获取顺序:对所有需多把锁的场景,约定资源 ID(如账户号、订单号)升序加锁,确保所有线程顺序一致。
-
使用带超时的锁:改用
ReentrantLock.tryLock(long, TimeUnit),获取失败则回滚或重试,避免无限等待。 - 缩小同步范围:避免在 synchronized 块内做耗时操作(如 I/O、远程调用),减少持锁时间,降低冲突概率。
-
用更高级的并发结构替代手动锁:如用
ConcurrentHashMap替代 synchronized Map,用StampedLock替代读写锁嵌套。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











