synchronized 不能自动避免死锁,需通过统一锁对象、避免嵌套同步调用、使用 jstack 验证锁地址、改用带超时的 ReentrantLock 等方式破坏死锁四条件。

synchronized 本身不会自动避免死锁,它只是提供互斥语义;死锁是否发生,取决于你如何组织锁的获取逻辑。关键不是“synchronized 怎么避免”,而是**怎么用 synchronized 满足死锁四条件中的至少一个被破坏**——尤其是打破“循环等待”和“持有并等待”。
统一锁对象,不混用 this 和 outer.this
内部类与外部类互相调用同步方法时,最容易因锁对象不一致引发死锁:
- 外部类方法用
synchronized(this)→ 锁的是外部类实例 - 内部类方法也用
synchronized(this)→ 锁的是内部类实例(和外部类实例地址不同) - 若线程 A 先锁外部实例再进内部类尝试锁内部实例,线程 B 反向操作,就构成闭环
✅ 正确做法:定义私有 final 锁对象,内外部临界区共用同一个对象,例如:private final Object stateLock = new Object();
所有 synchronized(stateLock) 都指向同一地址,JVM 能识别为同一把锁。
禁止在 synchronized 块内调用其他 synchronized 方法
这是隐式嵌套锁,极易引入不可见的持锁依赖:
- 外部类的
public synchronized void update()已隐式锁this - 内部类里写
synchronized(this) { outer.update(); }→ 先锁内部实例,再等外部实例(可能已被别的线程持住) - 反过来,外部类同步块里调内部类同步方法,同样危险
✅ 替代方案:把共享状态保护逻辑抽到统一锁块中,或改用 ReentrantLock.tryLock(timeout) 主动控制等待行为。
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
用 jstack -l 抓锁地址,验证是否真“一把锁”
死锁常因开发者误以为“都是 this 就是同一把锁”,但实际对象地址不同。jstack 是最直接的验证手段:
- 执行
jstack -l <pid> > dump.log(必须带-l) - 搜索
Found one Java-level deadlock,看 waiting to lock 和 locked 的地址是否成对出现 - 比对堆栈中
at xxx.java:23行,确认两个线程锁的是否真是不同对象(如0x000000076b0a1111vs0x000000076b0a2222)
地址不同 + 互相等待 = 确认是 synchronized 层面的死锁,不是业务卡顿。
必要时放弃阻塞,改用超时机制
synchronized 不支持超时,一旦卡住就是无限等待。若逻辑确实需要多把锁,优先转向显式锁:
- 用
ReentrantLock.tryLock(500, TimeUnit.MILLISECONDS)设定最大等待时间 - 失败后释放已持锁、记录日志、退避重试,而不是硬等
- 避免“先锁 A 再 tryLock B 失败却不放 A”的资源泄漏
这从根源上破坏了“持有并等待”条件,让系统具备自我恢复能力。

















