死锁可从根源避免,关键在于事前打破四个必要条件之一:统一锁获取顺序(如按id排序)、一次性申请全部资源并配合trylock超时、减小锁粒度或使用无锁结构、采用可中断锁与超时机制。

Java 线程死锁的四个必要条件——互斥、请求与保持、不可剥夺、循环等待——必须**同时成立**才会发生死锁。因此,在设计阶段主动打破其中任意一个,就能从根源上避免死锁。关键不在于“事后检测”,而在于“事前约束”。
统一锁获取顺序,打破循环等待
这是最常用也最有效的设计手段。只要所有线程以相同的全局顺序申请多个锁,就不可能形成环形等待链。
- 例如:约定所有涉及 accountA 和 accountB 的转账操作,都先锁 ID 小的账户,再锁 ID 大的账户
- 可封装为工具方法:lockInOrder(account1, account2),内部按 hashCode 或业务主键排序后加锁
- 避免硬编码顺序(如“先锁 A 再锁 B”),否则不同模块可能约定冲突
一次性申请全部所需资源,消除请求与保持
线程在开始操作前就明确所需全部锁,并尝试一次性获取;若任一锁不可得,立即释放已获锁并退让重试,不持有部分资源等待其他资源。
- 使用 tryLock() + 超时机制(如 ReentrantLock.tryLock(500, TimeUnit.MILLISECONDS))
- 失败后释放已持锁,短暂休眠后重试,避免忙等
- 注意:synchronized 不支持超时尝试,必须改用显式 Lock
避免嵌套锁或限制锁粒度,弱化互斥与持有行为
虽然互斥本身难以消除(否则就不是并发控制了),但可通过设计减少锁竞争范围和深度:
- 优先使用无锁结构(如 ConcurrentHashMap、AtomicInteger)替代同步块
- 将大锁拆分为多个细粒度锁(如按用户 ID 分段加锁),降低同时争抢概率
- 避免在持有锁期间调用外部方法(尤其是可能反向加锁的回调或服务)
使用可中断锁与超时机制,缓解不可剥夺的影响
不可剥夺是 Java 锁的固有特性,但可通过设计让线程“主动放弃”而非无限等待:
- 用 ReentrantLock.lockInterruptibly() 替代 synchronized,使线程能响应 interrupt 信号退出等待
- 所有阻塞操作(包括 wait、join、LockSupport.park)都应设置合理超时,防止永久挂起
- 配合监控告警:当某线程在锁上等待超过阈值(如 3 秒),记录堆栈并触发人工介入
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











