
可以利用 sleep() 不释放锁的特性,在单机环境下构造一个简单、可复现、无需竞态巧合的死锁测试案例。核心思路是:让两个线程以相反顺序获取两把锁,且在持有一把锁后,用 sleep() 强制“卡住”并等待另一把锁 —— 由于 sleep() 不释放已持有的锁,双方会永久僵持。
关键前提:sleep 不释放锁
这是构造确定性死锁的基础:
- 调用 Thread.sleep() 时,线程进入 TIMED_WAITING 状态;
- 它不会释放任何已持有的 synchronized 锁或 ReentrantLock;
- 因此,只要在同步块内调用 sleep,该锁就会被稳稳占住,直到 sleep 结束(或被中断)。
构造步骤(Java 示例)
以下代码可在任意 JDK 环境下稳定触发死锁,无需多轮重试:
- 定义两个对象作为锁:
lock1和lock2 - 线程 A:先 synchronized(lock1),再 sleep(100),再 synchronized(lock2)
- 线程 B:先 synchronized(lock2),再 sleep(100),再 synchronized(lock1)
- 启动两个线程,并确保它们几乎同时进入第一个同步块(可用 CountDownLatch 精确控制)
精简可运行代码
注意:此代码不处理中断,仅用于教学演示死锁机制
Object lock1 = new Object();
Object lock2 = new Object();
<p>Thread t1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("T1: got lock1");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println("T1: got lock2");
}
}
});</p><p>Thread t2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("T2: got lock2");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock1) {
System.out.println("T2: got lock1");
}
}
});</p><p>t1.start(); t2.start();
</p>
运行后,控制台输出:
T1: got lock1
T2: got lock2
→ 程序卡死,JVM 线程 dump 可清晰看到两个线程分别 BLOCKED 在对方持有的锁上。
为什么这个死锁是“确定性”的
区别于依赖调度时序的“概率性”死锁,本例通过 sleep(100) 实现了时间上的强制错开:
- 两个线程都能成功拿到第一把锁(无竞争);
- sleep 保证它们有充足时间进入“持有一锁、等另一锁”的状态;
- 此时双方都已持有锁且不释放,又都在等待对方释放,环路等待闭环形成;
- JVM 检测不到进展,死锁成立,且每次运行结果一致。










