死锁典型场景是多线程循环等待对方持有的锁,如线程a持lock1争lock2、线程b持lock2争lock1;判断依据是jstack显示blocked状态并提示“waiting to lock”;预防核心是按固定全局顺序加锁,如用identityhashcode统一锁获取顺序,或用trylock超时+回退机制,优先选用concurrenthashmap、stampedlock等高级并发工具,并启用jvm死锁检测与监控。

死锁发生的典型场景和判断依据
Java中死锁最常出现在多个线程循环等待对方持有的锁,比如线程A持有lock1并试图获取lock2,而线程B持有lock2并试图获取lock1。JVM本身不主动检测或中断死锁,但可通过jstack命令观察到java.lang.Thread.State: BLOCKED (on object monitor)并伴随“waiting to lock ”的提示——这通常是死锁的明确信号。
按固定顺序获取锁是最简单有效的预防手段
只要所有线程以相同全局顺序申请锁,循环等待条件就被打破。关键不是“谁先抢到”,而是“所有人按同一张清单排队”。
- 给锁对象定义可比较的标识,例如用
System.identityHashCode()生成唯一整数值,始终先获取哈希值小的那个锁 - 避免在业务逻辑里直接写
synchronized(objA) { synchronized(objB) { ... } }这种硬编码顺序,应封装成工具方法统一调度 - 若使用
ReentrantLock,记得配合tryLock(long, TimeUnit)设置超时,失败后释放已持锁并重试,而非无限制阻塞
用java.util.concurrent高级工具替代手动加锁
很多死锁源于对synchronized和wait/notify的误用。现代Java更推荐用线程安全的数据结构和同步机制来规避底层锁管理。
- 用
ConcurrentHashMap代替HashMap + synchronized,用AtomicInteger代替int + synchronized - 需要协调多个操作时,优先考虑
StampedLock的乐观读或Phaser这类可动态注册/注销的同步器,而非嵌套synchronized块 - 若必须多锁协作,用
LockSupport.parkNanos()配合自旋+状态检查,比盲目wait()更可控
启用JVM内置死锁检测并集成到监控流程
开发和测试阶段就该让死锁暴露出来,而不是等线上卡住才排查。
立即学习“Java免费学习笔记(深入)”;
- 启动JVM时加上
-XX:+PrintConcurrentLocks(配合-XX:+UnlockDiagnosticVMOptions),或运行时调用ManagementFactory.getThreadMXBean().findDeadlockedThreads()主动扫描 - 在Spring Boot应用中,可将
ThreadMXBean检测逻辑注册为@Scheduled任务,定期输出死锁线程栈到日志 - 注意:
findDeadlockedThreads()只检测Object monitor和java.util.concurrent锁,不包括Unsafe.park等底层阻塞,需结合jstack -l交叉验证
真正难防的不是标准死锁,而是那些混用synchronized、ReentrantLock、CompletableFuture回调和线程池拒绝策略的复合场景——锁的边界变得模糊,等待链不再线性可见。这时候光靠顺序加锁不够,得从任务拆分粒度和资源生命周期入手重新设计。










