哈哈,这个题目太真实了——写 Java 的人谁没被死锁坑过几次呢?😅
我帮你梳理一下:
1. 什么是死锁?
在多线程程序中,如果两个(或多个)线程互相等待对方持有的锁而不释放,就会形成死锁。比如:
public class DeadlockDemo {
private final Object lockA = new Object();
private final Object lockB = new Object();
public void method1() {
synchronized (lockA) {
System.out.println("Thread " + Thread.currentThread().getName() + " got lockA");
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
synchronized (lockB) {
System.out.println("Thread " + Thread.currentThread().getName() + " got lockB");
}
}
}
public void method2() {
synchronized (lockB) {
System.out.println("Thread " + Thread.currentThread().getName() + " got lockB");
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
synchronized (lockA) {
System.out.println("Thread " + Thread.currentThread().getName() + " got lockA");
}
}
}
}
两个线程一个先拿 A 再拿 B,一个先拿 B 再拿 A,就很容易死锁。
2. 如何发现死锁?
- JDK 工具
jconsole
或jvisualvm
可以直接检测线程死锁。jstack <pid>
打印线程堆栈,能看到Found one Java-level deadlock:
提示。
- 日志定位
- 在加锁的地方打印线程信息,发现某些线程长期阻塞。
3. 如何避免死锁?
主要有几条原则:
- 固定锁顺序
所有线程获取锁的顺序必须一致,比如大家都先拿lockA
,再拿lockB
。 - 尝试锁 + 超时
使用ReentrantLock.tryLock(timeout, unit)
,避免无限等待。ReentrantLock lockA = new ReentrantLock(); ReentrantLock lockB = new ReentrantLock(); public void safeMethod() throws InterruptedException { if (lockA.tryLock(100, TimeUnit.MILLISECONDS)) { try { if (lockB.tryLock(100, TimeUnit.MILLISECONDS)) { try { // safe work } finally { lockB.unlock(); } } } finally { lockA.unlock(); } } }
- 减少锁的粒度和持有时间
不要在锁里做 I/O、网络、sleep 等耗时操作。 - 使用并发工具类
java.util.concurrent
里的ConcurrentHashMap
、BlockingQueue
等,很多场景可以避免手动加锁。
4. 如果真的遇到了死锁怎么办?
- 线上紧急处理:
kill -3 <pid>
打印线程堆栈,确认死锁点。- 重启服务(应急手段)。
- 根本解决:
- 分析死锁的锁顺序,重构代码,统一加锁顺序。
- 或者改用无锁/更细粒度的数据结构。
好嘞,那我给你一个完整的小示例:
先写一个“必死锁”的 Demo,然后再写一个“改造后的安全版本”。
🔴 死锁示例(必现)
public class DeadlockExample {
private final Object lockA = new Object();
private final Object lockB = new Object();
// 线程1:先拿 lockA,再拿 lockB
public void methodA() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + " 拿到 lockA");
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + " 拿到 lockB");
}
}
}
// 线程2:先拿 lockB,再拿 lockA
public void methodB() {
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + " 拿到 lockB");
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + " 拿到 lockA");
}
}
}
public static void main(String[] args) {
DeadlockExample example = new DeadlockExample();
Thread t1 = new Thread(example::methodA, "线程1");
Thread t2 = new Thread(example::methodB, "线程2");
t1.start();
t2.start();
}
}
👉 运行后你会发现输出类似:
线程1 拿到 lockA
线程2 拿到 lockB
然后就卡住了,再也没有后续输出,这就是死锁。
✅ 改造后的安全版本(固定锁顺序)
public class FixedLockExample {
private final Object lockA = new Object();
private final Object lockB = new Object();
// 所有线程都严格先拿 lockA,再拿 lockB
public void methodSafe(String name) {
synchronized (lockA) {
System.out.println(name + " 拿到 lockA");
try { Thread.sleep(100); } catch (InterruptedException ignored) {}
synchronized (lockB) {
System.out.println(name + " 拿到 lockB");
}
}
}
public static void main(String[] args) {
FixedLockExample example = new FixedLockExample();
Thread t1 = new Thread(() -> example.methodSafe("线程1"));
Thread t2 = new Thread(() -> example.methodSafe("线程2"));
t1.start();
t2.start();
}
}
👉 这次运行不会死锁,因为大家都遵循同一个加锁顺序。
✅ 改造后的安全版本(ReentrantLock + tryLock 超时)
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TryLockExample {
private final ReentrantLock lockA = new ReentrantLock();
private final ReentrantLock lockB = new ReentrantLock();
public void safeMethod(String name) {
try {
if (lockA.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
if (lockB.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
System.out.println(name + " 成功拿到 lockA 和 lockB");
} finally {
lockB.unlock();
}
} else {
System.out.println(name + " 拿 lockB 超时,放弃");
}
} finally {
lockA.unlock();
}
} else {
System.out.println(name + " 拿 lockA 超时,放弃");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public static void main(String[] args) {
TryLockExample example = new TryLockExample();
Thread t1 = new Thread(() -> example.safeMethod("线程1"));
Thread t2 = new Thread(() -> example.safeMethod("线程2"));
t1.start();
t2.start();
}
}
👉 即使竞争同一把锁,也不会“死等”,最多超时报错退出,避免死锁。
发表回复