Java线程死锁可稳定复现于两个线程以相反顺序获取同一组锁且无超时机制时;典型表现为程序卡住不输出、不退出,jstack可检测到“Found one Java-level deadlock”,并显示线程持有与等待锁的完整关系。

Java 线程死锁可以通过构造两个或多个线程,让它们以相反的顺序获取同一组锁来稳定复现。关键在于:**确保锁的获取顺序不一致,且没有超时或中断机制**。
一、编写可复现的死锁测试代码
下面是一个经典双线程+两把锁的死锁示例:
注意:运行后程序会卡住(无输出、不退出),说明已进入死锁状态。
public class DeadlockDemo {
private static final Object lockA = new Object();
private static final Object lockB = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
synchronized (lockA) {
System.out.println("Thread-1: acquired lockA");
try { Thread.sleep(100); } catch (InterruptedException e) { }
synchronized (lockB) {
System.out.println("Thread-1: acquired lockB");
}
}
}, "Thread-1");
Thread t2 = new Thread(() -> {
synchronized (lockB) {
System.out.println("Thread-2: acquired lockB");
try { Thread.sleep(100); } catch (InterruptedException e) { }
synchronized (lockA) {
System.out.println("Thread-2: acquired lockA");
}
}
}, "Thread-2");
t1.start();
t2.start();
t1.join();
t2.join();
}
}
二、快速检测死锁的方法
Java 自带工具能直接识别死锁线程,无需额外依赖:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
-
jstack + 进程 PID:运行死锁程序后,在终端执行
jstack <pid>,搜索关键词Found 1 deadlock.,会清晰列出每个线程持有的锁和等待的锁。 -
JConsole 可视化工具:启动
jconsole→ 连接目标进程 → “Threads” 标签页 → 点击 “Detect Deadlock” 按钮,自动高亮死锁线程并展示调用栈。 -
代码中主动检测:使用
ThreadMXBean查询:
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long[] deadlockedIds = bean.findDeadlockedThreads(); // 返回死锁线程 ID 数组
if (deadlockedIds != null && deadlockedIds.length > 0) {
ThreadInfo[] infos = bean.getThreadInfo(deadlockedIds, true, true);
for (ThreadInfo info : infos) {
System.out.println("Deadlocked thread: " + info.getThreadName());
System.out.println("Stack trace:\n" + Arrays.toString(info.getStackTrace()));
}
}
三、避免死锁的实用建议
复现只是手段,根治靠设计规范:
- 统一加锁顺序:对多个锁按固定规则排序(如按对象哈希值、类名、字段名等),所有线程严格按此顺序获取。
-
使用 tryLock() 设置超时:改用
ReentrantLock.tryLock(long, TimeUnit),失败则释放已持锁并重试或回退,打破循环等待。 -
缩小锁粒度 & 减少嵌套:避免在持有锁期间调用外部方法(尤其可能阻塞或加锁的操作);优先考虑无锁结构(如
ConcurrentHashMap、AtomicInteger)。 -
用 jcmd 替代 jstack(JDK 7+):命令更简洁,例如
jcmd <pid> VM.native_memory summary或jcmd <pid> Thread.print。
四、进阶:用单元测试捕获死锁(JUnit 5)
借助 CompletableFuture 和超时控制,让测试自动失败而非挂起:
@Test
void shouldDetectDeadlockWithinTimeout() throws Exception {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
// 启动上述死锁逻辑(封装为方法)
triggerDeadlock();
});
assertThrows(ExecutionException.class, () ->
future.orTimeout(3, TimeUnit.SECONDS).join()
);
}
这样测试会在 3 秒后抛异常并结束,便于 CI 环境集成。

















