synchronized 不处理死锁,需开发者主动规避:统一加锁顺序(破坏循环等待)、减少锁粒度与嵌套、改用 reentrantlock 的 trylock 超时机制、结合 jstack 等工具诊断。

synchronized 本身不处理死锁问题,它只是 Java 提供的内置锁机制,具备互斥、不可抢占、占有并等待等特性——恰恰是这些特性,在多锁场景下容易诱发死锁。Java 不会在 synchronized 层面自动检测、预防或打破死锁。能否避免死锁,完全取决于开发者如何组织加锁逻辑。
要真正应对死锁,必须在 synchronized 的使用方式上主动采取规避策略。以下是几种切实可行、被广泛验证的方法:
统一加锁顺序(破坏循环等待)
这是最直接、最有效的预防手段。只要所有线程以完全相同的顺序获取多个对象锁,就不会形成等待环路。
private final Object lockA = new Object();
private final Object lockB = new Object();
// ✅ 正确:始终先 lockA,再 lockB
public void doWork1() {
synchronized (lockA) {
synchronized (lockB) {
// 操作
}
}
}
// ✅ 正确:同样先 lockA,再 lockB
public void doWork2() {
synchronized (lockA) {
synchronized (lockB) {
// 操作
}
}
}
⚠️ 错误示范(导致死锁):
- 线程1:
synchronized(lockA) → synchronized(lockB) - 线程2:
synchronized(lockB) → synchronized(lockA)
→ 极易陷入 T1 等 B、T2 等 A 的循环等待。
建议给锁对象定义明确优先级(如按 System.identityHashCode() 排序),动态决定加锁次序,适用于锁对象不确定的场景。
减少锁粒度与嵌套深度
synchronized 嵌套越深、持有时间越长,死锁风险越高。应尽量做到:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 避免在
synchronized块内调用外部方法(尤其可能触发另一次加锁的操作) - 把耗时操作(如 I/O、网络、复杂计算)移出同步块
- 用更细粒度的锁替代大对象锁(例如用
ConcurrentHashMap替代synchronized(map))
使用带超时的替代方案(需切换为 Lock)
synchronized 不支持超时尝试,这是它的硬限制。若需“尝试获取、失败即退”,必须改用 java.util.concurrent.locks.ReentrantLock 的 tryLock(long, TimeUnit):
private final ReentrantLock lockA = new ReentrantLock();
private final ReentrantLock lockB = new ReentrantLock();
public boolean tryDoWork() {
if (lockA.tryLock()) {
try {
if (lockB.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
// 成功获得两把锁
return true;
} finally {
lockB.unlock();
}
}
} finally {
lockA.unlock();
}
}
return false;
}
这种方式能主动打破“占有并等待”条件,配合随机重试延迟,可显著降低死锁概率。
主动检测与诊断(运行时辅助)
虽然 synchronized 无法自愈死锁,但 JDK 提供了成熟工具定位问题:
-
jstack <pid></pid>:输出线程堆栈,会明确标注Found one Java-level deadlock - JConsole / VisualVM:图形化查看线程状态和锁持有关系
-
-XX:+PrintConcurrentLocks或jcmd <pid> VM.native_memory summary</pid>(辅助排查)
发现死锁后,应结合代码回溯锁获取路径,修正顺序或重构同步边界。
基本上就这些。关键不是依赖 synchronized 做什么,而是你用它时有没有守住顺序、范围和超时意识。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










