防止 synchronized 嵌套死锁的核心是打破循环等待和持有并等待:统一多锁获取顺序(如按 identityHashCode 排序)、缩小同步范围、避免锁内调用外部方法、改用 tryLock 超时机制或更高阶并发工具。

防止多个 synchronized 嵌套调用引发死锁,核心是打破“循环等待”和“持有并等待”这两个必要条件。Java 中的 synchronized 本身是可重入锁,单个线程重复锁同一对象不会死锁;真正危险的是**多个线程交叉获取不同锁**——比如线程 A 先锁 lockA 再等 lockB,线程 B 先锁 lockB 再等 lockA。
统一锁获取顺序
让所有线程按相同规则申请多个锁,从根源上消除循环等待链。
- 对锁对象做唯一标识,例如用
System.identityHashCode()(比hashCode()更稳定)排序 - 始终先获取哈希值小的锁,再获取大的锁
- 避免硬编码顺序(如“先 accountA 后 accountB”),防止业务逻辑变更导致顺序不一致
缩小同步范围,避免在锁内调用外部方法
嵌套死锁常因锁中调用了可能间接持锁的代码而触发,比如调用某个服务方法,而该方法内部又用了另一把锁。
- 只把真正需要保护的临界区代码包进
synchronized块 - 提前计算好所需数据,释放锁后再做日志、网络、数据库等耗时或不可控操作
- 特别警惕
toString()、equals()、回调函数等隐式调用场景
改用显式锁 + 超时机制
当必须操作多个资源且顺序难统一时,ReentrantLock.tryLock(long, TimeUnit) 是更可控的选择。
- 尝试获取第一把锁,成功后再尝试第二把,任一失败就立即释放已持锁
- 配合退避重试(如指数退避),降低竞争概率
- 注意:不要在
tryLock失败后直接sleep然后重试——这会浪费线程资源,应结合业务逻辑判断是否降级或报错
用更高层级的并发工具替代多锁
很多看似需要多把锁的场景,其实可通过设计优化绕开。
- 转账类操作:用全局唯一锁(如
synchronized(Account.class))或基于账户 ID 的分段锁(如lock[abs(id1 % N)]) - 状态更新类:用
AtomicReference.compareAndSet()或StampedLock乐观读 - 集合操作:优先选
ConcurrentHashMap、CopyOnWriteArrayList等线程安全容器,而非手动加锁遍历

















