预防死锁重在设计,核心是切断循环等待链:统一锁获取顺序、用带超时的reentrantlock替代synchronized、减少锁粒度、主动检测监控。

避免死锁重在预防设计,排查则依赖工具与监控。核心不是等卡住再救,而是从加锁逻辑源头切断循环等待链。
统一锁获取顺序
多个锁必须按固定全局顺序申请,打破“循环等待”条件。不要写 synchronized(a) { synchronized(b) { ... } } 这类硬编码嵌套,而应封装为可复用的加锁策略:
- 对锁对象调用
System.identityHashCode(),始终先获取哈希值小的那个锁 - 为业务资源定义层级编号(如账户锁=1、订单锁=2、库存锁=3),所有线程严格按编号升序申请
- 若涉及动态锁对象,可提前排序后批量加锁,避免中途插入新锁
用带超时的锁替代无条件阻塞
synchronized 无法中断或超时,容易陷入无限等待。改用 ReentrantLock.tryLock(long, TimeUnit) 可主动控制风险:
- 尝试获取第一个锁成功后,再以短超时(如 100ms)尝试第二个锁
- 任一锁获取失败,立即释放已持锁,并可选择重试、降级或抛出业务异常
- 配合
lockInterruptibly()支持线程中断,便于上层统一管控生命周期
减少锁粒度与嵌套,优先选用无锁结构
很多死锁源于过度依赖手动加锁。能不锁就不锁,能小锁不大锁:
- 用
ConcurrentHashMap替代HashMap + synchronized - 用
AtomicInteger或LongAdder替代int + synchronized - 读多写少场景考虑
StampedLock的乐观读,避免读写互斥 - 需协调多阶段操作时,用
Phaser或CountDownLatch替代多层synchronized块
主动检测与线上监控
开发和上线后都要让死锁“露出来”,而不是等用户反馈卡顿:
- 本地调试用
jstack <pid></pid>,搜索Found one Java-level deadlock,它会直接标出互相等待的线程和锁对象 - JVM 启动参数加入
-XX:+PrintConcurrentLocks(需搭配-XX:+UnlockDiagnosticVMOptions)输出锁持有详情 - 在 Spring Boot 中定时调用
ManagementFactory.getThreadMXBean().findDeadlockedThreads(),发现即记录栈信息并告警 - 注意:
findDeadlockedThreads()不覆盖Unsafe.park类底层阻塞,需结合jstack -l交叉验证
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











