Java防范死锁的核心是打破持有并等待和循环等待条件:统一锁获取顺序(如按哈希码升序加锁)、使用tryLock超时机制主动回退、缩小锁粒度与避免嵌套、结合ThreadMXBean定期检测。

Java 中防范死锁,核心是打破死锁四条件中的至少一个——互斥、持有并等待、不可剥夺、循环等待。实际开发中无法消除互斥(这是同步的基础),也不宜强行剥夺锁(破坏设计契约),因此重点落在控制“持有并等待”和“循环等待”上。以下是最常用且有效的几种实践方式。
统一锁获取顺序
这是最简单也最有效的预防手段。只要所有线程以完全相同的顺序申请多个锁,就不可能形成循环等待环。
- 例如:约定所有代码必须先 lockA,再 lockB;禁止出现先 lockB 后 lockA 的路径
- 可将资源抽象为有明确优先级的对象(如按对象哈希码排序),在加锁前先对锁对象排序,再依次获取
- 注意:该规则需覆盖全部调用路径,包括间接调用(如工具类、回调、嵌套方法)
使用带超时的锁尝试(tryLock)
避免无限期阻塞,让线程有机会主动退出或重试,从而打破“持有并等待”的持续状态。
- 用 ReentrantLock.tryLock(long, TimeUnit) 替代 synchronized 或无参 tryLock
- 若获取锁失败,可释放已持有的锁、回退操作、短暂休眠后重试,或转为异步处理
- 示例:获取 lock1 成功后,用 tryLock(100, MILLISECONDS) 获取 lock2;失败则 unlock(lock1) 并返回错误
减少锁粒度与嵌套深度
锁得越少、越短、越不交叉,死锁风险越低。过度同步是死锁温床。
立即学习“Java免费学习笔记(深入)”;
- 避免在 synchronized 块内调用外部方法(尤其不可控的第三方或回调),防止隐式锁竞争
- 拆分大锁为多个细粒度锁(如按 ID 分段加锁),但需权衡并发吞吐与逻辑一致性
- 优先使用无锁结构(如 ConcurrentHashMap、AtomicInteger)或不可变对象,从源头规避锁需求
定期检测与快速暴露
防御性编程不能完全杜绝死锁,但可通过监控让问题早发现、易定位。
- 启用 JVM 参数 -XX:+PrintConcurrentLocks 或使用 jstack 查看线程堆栈,识别 blocked 状态及锁持有关系
- 在关键服务中集成周期性检测:ThreadMXBean.findDeadlockedThreads() 返回死锁线程 ID,触发告警或自动 dump
- 单元测试中模拟多线程竞争路径,结合 Awaitility 等工具验证锁行为是否符合预期


















