跳到主要内容

死锁

问题

什么是死锁?产生死锁的条件是什么?如何避免?

答案

死锁的定义

两个或多个线程/进程互相持有对方需要的资源,导致所有线程都无法继续执行。

死锁四个必要条件

同时满足以下四个条件才会发生死锁:

条件说明破坏方式
互斥资源一次只能被一个线程持有用无锁数据结构(CAS)
持有并等待持有资源的同时等待其他资源一次性申请所有资源
不可抢占资源不能被强制释放超时释放
循环等待形成等待环按固定顺序加锁

Java 死锁示例

典型死锁代码
Object lock1 = new Object();
Object lock2 = new Object();

// 线程 A:先锁 1,再锁 2
new Thread(() -> {
synchronized (lock1) {
Thread.sleep(100); // 给线程 B 时间拿到 lock2
synchronized (lock2) { // 等待线程 B 释放 lock2 → 死锁
System.out.println("线程 A");
}
}
}).start();

// 线程 B:先锁 2,再锁 1
new Thread(() -> {
synchronized (lock2) {
Thread.sleep(100); // 给线程 A 时间拿到 lock1
synchronized (lock1) { // 等待线程 A 释放 lock1 → 死锁
System.out.println("线程 B");
}
}
}).start();

避免死锁

方案 1:按固定顺序加锁(推荐)
// 不管哪个线程,总是先锁 lock1 再锁 lock2
// 打破循环等待条件
synchronized (lock1) {
synchronized (lock2) {
// 业务逻辑
}
}
方案 2:使用 tryLock 超时
ReentrantLock lockA = new ReentrantLock();
ReentrantLock lockB = new ReentrantLock();

if (lockA.tryLock(1, TimeUnit.SECONDS)) {
try {
if (lockB.tryLock(1, TimeUnit.SECONDS)) {
try {
// 业务逻辑
} finally { lockB.unlock(); }
}
} finally { lockA.unlock(); }
}

排查死锁

# 方式 1:jstack 打印线程堆栈
jstack <pid> | grep -A 20 "deadlock"

# 方式 2:jconsole/jvisualvm 图形化工具

# 方式 3:代码中检测
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long[] threadIds = bean.findDeadlockedThreads();

常见面试问题

Q1: 如何排查线上死锁?

答案

  1. jstack <pid>:输出所有线程的堆栈,JVM 会自动检测死锁并打印
  2. Arthas thread -b:直接找到阻塞线程
  3. 看日志和监控:关注线程池是否打满、请求超时

Q2: 数据库死锁怎么处理?

答案

MySQL InnoDB 有死锁检测机制(innodb_deadlock_detect),检测到死锁后自动回滚持有锁最少的事务。

预防方式:

  1. 按固定顺序访问表和行
  2. 大事务拆小事务
  3. 合理使用索引,减少锁范围

详见 MySQL 锁机制

相关链接