死锁可通过两线程互持对方所需锁并尝试获取对方锁复现,需同时满足互斥、占有并等待、不可剥夺、循环等待四条件;典型场景是synchronized嵌套加锁。

死锁可以通过两个线程分别持有对方需要的锁,并尝试获取对方已持有的锁来复现,synchronized 的嵌套加锁(即在已持有一个锁的情况下,再去获取另一个锁)是典型的触发场景。
死锁复现的关键条件
必须同时满足以下四个条件(死锁四必要条件):
- 互斥条件:锁是独占的,一个线程持有后其他线程无法进入临界区
- 占有并等待:线程已持有至少一个锁,又在等待获取其他被占用的锁
- 不可剥夺:锁不能被系统强制收回,只能由持有线程主动释放
- 循环等待:存在线程 A 等待 B、B 等待 C、C 又等待 A 的闭环
用 synchronized 嵌套加锁复现死锁的代码示例
下面是一个经典双资源死锁示例(模拟转账场景),两个线程分别按不同顺序对两个账户对象加锁:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
public class DeadlockDemo {
private static final Object accountA = new Object();
private static final Object accountB = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (accountA) {
System.out.println("Thread-1: locked accountA");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (accountB) {
System.out.println("Thread-1: locked accountB");
}
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (accountB) {
System.out.println("Thread-2: locked accountB");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (accountA) {
System.out.println("Thread-2: locked accountA");
}
}
}, "t2");
t1.start();
t2.start();
}
}
运行后大概率出现:t1 持有 accountA 并等待 accountB,t2 持有 accountB 并等待 accountA —— 双方永久阻塞,死锁形成。
如何验证是否发生死锁
Java 提供了多种方式检测死锁:
- 使用 jstack
查看线程堆栈,搜索 “deadlock” 或观察多个线程处于 BLOCKED 状态且锁等待链成环 - 在代码中调用 ThreadMXBean.findDeadlockedThreads() 主动检测(需在 JVM 启动时启用 -XX:+UseLockProfiler)
- 借助 JConsole 或 VisualVM 连接运行中的进程,在“线程”页点击“Detect Deadlock”按钮
避免 synchronized 嵌套死锁的实用建议
核心思路是打破“循环等待”或“占有并等待”:
-
固定加锁顺序:所有线程按同一规则(如对象哈希值升序)获取多个锁。例如:
if (a.hashCode() - 使用超时机制:改用 ReentrantLock.tryLock(long, TimeUnit),获取失败则释放已持锁并重试/回退
- 减少锁粒度与嵌套深度:避免在 synchronized 块内再调用可能加锁的外部方法;优先使用局部变量、无状态设计
- 避免锁内执行耗时操作:如 I/O、网络调用、长循环等,降低锁持有时间,间接减少死锁概率
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










