按固定顺序获取锁是最简单有效的预防死锁手段,即为所有资源定义唯一可比标识并按升序加锁;配合trylock超时、缩小锁粒度、运行时检测及分布式协同控制,全面提升死锁防控能力。

按固定顺序获取锁是最简单有效的预防手段
死锁最常见原因是多个线程以不同顺序请求同一组锁,比如线程A先锁resourceA再锁resourceB,而线程B反过来。只要强制所有线程按全局一致的顺序加锁,就能从根源上切断循环等待条件。
实操建议:
- 为所有可锁定资源定义唯一、可比较的标识(如ID、类名、字符串字典序),例如用
System.identityHashCode(obj)生成稳定整数序号 - 在获取多个锁前,先对锁对象排序,再按升序依次调用
lock.lock() - 避免直接对业务对象(如
user、order)加锁,改用专用锁对象并明确其排序规则 - 注意:
synchronized块无法动态排序,这种策略更适合ReentrantLock场景
使用tryLock()配合超时,主动打破死锁可能
ReentrantLock.tryLock(long, TimeUnit)能避免无限等待,是防御性编程的关键工具。它不保证获得锁,但能让你在拿不到锁时及时回退,而不是卡死。
常见错误现象:用lock.lock()连续获取两把锁,第二把失败时第一把未释放,导致其他线程永远阻塞。
立即学习“Java免费学习笔记(深入)”;
正确做法:
- 每次
tryLock()后必须检查返回值,true才执行临界区,false则立即释放已持有的锁 - 超时时间不宜过短(如10ms),也不宜过长(如30s);建议设为业务预期处理时间的2–3倍
- 重试逻辑要加退避(如指数退避),避免密集争抢加剧竞争
- 注意:
synchronized不支持超时,必须切换到ReentrantLock
减少锁粒度和锁持有时间,从源头压缩风险窗口
锁范围越大、持有越久,发生交叉等待的概率就越高。很多“看似必要”的同步,其实可以拆解或移出临界区。
使用场景举例:
- 不要在
synchronized块里调用外部服务、IO操作或复杂计算——这些应移到锁外 - 用
ConcurrentHashMap代替HashMap + synchronized,避免整个map被锁住 - 考虑分段锁(如
ConcurrentHashMap内部的Segment)或读写锁(ReentrantReadWriteLock),让读操作不互斥 - 避免嵌套同步块,尤其不要在持有锁时调用可能再次加锁的第三方方法
用jstack或JFR定位潜在死锁点,别只靠推测
即使做了预防,复杂系统仍可能因间接依赖(如A→B→C→A调用链)触发死锁。运行时检测比纯编码约定更可靠。
关键操作:
- 遇到线程长时间WAITING/BLOCKED,立刻执行
jstack <pid></pid>,搜索found 1 deadlock字样 - JDK8+可用
jcmd <pid> VM.native_memory summary</pid>辅助判断内存相关锁争用 - 生产环境开启JFR(Java Flight Recorder)录制,筛选
java.lang.Thread#ThreadPark事件,结合栈帧分析锁获取路径 - 注意:jstack输出中显示的
locked地址要和waiting to lock对比,相同地址才构成闭环










