核心概念
死锁(Deadlock)是指两个或多个线程互相持有对方需要的资源,导致所有线程都无法继续执行的状态。
线程A持有资源1,等待资源2
↓
[死锁]
↑
线程B持有资源2,等待资源1
死锁示例
经典死锁场景
public class DeadlockDemo {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
// 线程1:先获取lock1,再获取lock2
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("线程1获取lock1");
try {
Thread.sleep(100); // 增加死锁概率
} catch (InterruptedException e) {
}
System.out.println("线程1等待lock2...");
synchronized (lock2) {
System.out.println("线程1获取lock2");
}
}
}, "Thread-1");
// 线程2:先获取lock2,再获取lock1
Thread thread2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("线程2获取lock2");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
System.out.println("线程2等待lock1...");
synchronized (lock1) {
System.out.println("线程2获取lock1");
}
}
}, "Thread-2");
thread1.start();
thread2.start();
}
}
输出:
线程1获取lock1
线程2获取lock2
线程1等待lock2...
线程2等待lock1...
// 程序卡死,死锁发生
死锁产生的四个必要条件
1. 互斥条件(Mutual Exclusion)
定义:资源只能被一个线程占用,其他线程必须等待。
synchronized (lock) {
// 同一时刻只有一个线程能进入
}
2. 请求与保持条件(Hold and Wait)
定义:线程已持有至少一个资源,同时又请求其他资源。
synchronized (lock1) { // 持有lock1
synchronized (lock2) { // 请求lock2
// ...
}
}
3. 不可剥夺条件(No Preemption)
定义:线程持有的资源不能被强制剥夺,只能主动释放。
// synchronized锁无法被外部中断
// 线程只能等待或自己释放
4. 循环等待条件(Circular Wait)
定义:存在线程资源的循环等待链。
线程A → 资源1 → 线程B → 资源2 → 线程A
↑___________________________________|
循环等待
破坏任意一个条件即可避免死锁。
死锁检测
1. jstack命令行工具
# 1. 找到Java进程PID
jps -l
# 2. 打印线程堆栈
jstack <pid>
# 输出示例:
Found one Java-level deadlock:
=============================
"Thread-2":
waiting to lock monitor 0x00007f8b8c004e00 (object 0x000000076ab48d98, a java.lang.Object),
which is held by "Thread-1"
"Thread-1":
waiting to lock monitor 0x00007f8b8c007350 (object 0x000000076ab48da8, a java.lang.Object),
which is held by "Thread-2"
Java stack information for the threads listed above:
===================================================
"Thread-2":
at DeadlockDemo.lambda$main$1(DeadlockDemo.java:30)
- waiting to lock <0x000000076ab48d98> (a java.lang.Object)
- locked <0x000000076ab48da8> (a java.lang.Object)
2. JConsole图形化工具
// 启动JConsole
// 1. 运行:jconsole
// 2. 连接到目标进程
// 3. 切换到"线程"标签
// 4. 点击"检测死锁"按钮
3. ThreadMXBean编程检测
public class DeadlockDetector {
public static void detectDeadlock() {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 检测死锁
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null) {
System.out.println("检测到死锁!");
ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(deadlockedThreads);
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("线程名: " + threadInfo.getThreadName());
System.out.println("线程状态: " + threadInfo.getThreadState());
System.out.println("锁名: " + threadInfo.getLockName());
System.out.println("锁持有者: " + threadInfo.getLockOwnerName());
System.out.println("---");
}
} else {
System.out.println("未检测到死锁");
}
}
public static void main(String[] args) throws InterruptedException {
// 启动死锁场景
DeadlockDemo.main(args);
// 等待死锁发生
Thread.sleep(1000);
// 检测死锁
detectDeadlock();
}
}
4. 定时监控
public class DeadlockMonitor {
private final ScheduledExecutorService scheduler =
Executors.newScheduledThreadPool(1);
private final ThreadMXBean threadMXBean =
ManagementFactory.getThreadMXBean();
public void startMonitoring() {
scheduler.scheduleAtFixedRate(() -> {
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null) {
System.err.println("【告警】检测到死锁,涉及线程数: " +
deadlockedThreads.length);
// 发送告警、记录日志等
handleDeadlock(deadlockedThreads);
}
}, 0, 10, TimeUnit.SECONDS); // 每10秒检测一次
}
private void handleDeadlock(long[] threadIds) {
ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadIds, true, true);
for (ThreadInfo info : threadInfos) {
System.err.println("死锁线程: " + info.getThreadName());
System.err.println("堆栈信息:");
for (StackTraceElement element : info.getStackTrace()) {
System.err.println(" " + element);
}
}
}
}
死锁解决方案
方案1:破坏请求与保持条件(一次性申请所有资源)
// ❌ 原始代码(可能死锁)
public class BankTransfer {
public void transfer(Account from, Account to, int amount) {
synchronized (from) { // 持有from
synchronized (to) { // 请求to
from.debit(amount);
to.credit(amount);
}
}
}
}
// ✅ 方案1:一次性申请所有资源
public class BankTransferFixed1 {
private static final Object tieLock = new Object();
public void transfer(Account from, Account to, int amount) {
synchronized (tieLock) { // 先获取全局锁
synchronized (from) {
synchronized (to) {
from.debit(amount);
to.credit(amount);
}
}
}
}
}
缺点:降低并发度。
方案2:破坏不可剥夺条件(超时释放)
// ✅ 使用tryLock超时机制
public class BankTransferFixed2 {
public boolean transfer(Account from, Account to, int amount)
throws InterruptedException {
boolean fromLocked = false;
boolean toLocked = false;
try {
// 尝试获取from锁,超时1秒
fromLocked = from.getLock().tryLock(1, TimeUnit.SECONDS);
if (!fromLocked) {
return false; // 获取失败,返回
}
// 尝试获取to锁,超时1秒
toLocked = to.getLock().tryLock(1, TimeUnit.SECONDS);
if (!toLocked) {
return false; // 获取失败,返回
}
// 执行转账
from.debit(amount);
to.credit(amount);
return true;
} finally {
if (fromLocked) from.getLock().unlock();
if (toLocked) to.getLock().unlock();
}
}
}
方案3:破坏循环等待条件(资源排序)
// ✅ 按照统一顺序获取锁
public class BankTransferFixed3 {
public void transfer(Account from, Account to, int amount) {
// 按照账户ID排序
Account first = from.getId() < to.getId() ? from : to;
Account second = from.getId() < to.getId() ? to : from;
synchronized (first) { // 先获取ID小的锁
synchronized (second) { // 再获取ID大的锁
from.debit(amount);
to.credit(amount);
}
}
}
}
class Account {
private final int id;
private int balance;
public Account(int id, int balance) {
this.id = id;
this.balance = balance;
}
public int getId() {
return id;
}
public synchronized void debit(int amount) {
balance -= amount;
}
public synchronized void credit(int amount) {
balance += amount;
}
}
推荐:这是最常用的死锁预防方案。
方案4:使用并发工具类
// ✅ 使用Semaphore
public class BankTransferFixed4 {
private final Semaphore semaphore = new Semaphore(1);
public void transfer(Account from, Account to, int amount)
throws InterruptedException {
semaphore.acquire(); // 同一时刻只允许一个转账
try {
from.debit(amount);
to.credit(amount);
} finally {
semaphore.release();
}
}
}
// ✅ 使用ReadWriteLock
public class DataManager {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private Map<String, String> data = new HashMap<>();
public String read(String key) {
lock.readLock().lock(); // 读锁不会死锁
try {
return data.get(key);
} finally {
lock.readLock().unlock();
}
}
public void write(String key, String value) {
lock.writeLock().lock();
try {
data.put(key, value);
} finally {
lock.writeLock().unlock();
}
}
}
避免死锁的最佳实践
1. 锁顺序一致
// ✅ 所有地方都按相同顺序获取锁
public class LockOrdering {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
private static final Object lock3 = new Object();
public void method1() {
synchronized (lock1) {
synchronized (lock2) {
synchronized (lock3) {
// 业务逻辑
}
}
}
}
public void method2() {
// 必须保持相同顺序:lock1 → lock2 → lock3
synchronized (lock1) {
synchronized (lock2) {
// 业务逻辑
}
}
}
}
2. 缩小锁范围
// ❌ 锁范围过大
public synchronized void largeMethod() {
// 100行代码
// 只有最后10行需要同步
}
// ✅ 缩小锁范围
public void optimizedMethod() {
// 90行不需要同步的代码
synchronized (this) {
// 只同步必要的10行
}
}
3. 使用tryLock超时
// ✅ 设置超时,避免无限等待
Lock lock = new ReentrantLock();
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// 业务逻辑
} finally {
lock.unlock();
}
} else {
// 获取锁失败,执行降级逻辑
handleLockFailure();
}
4. 避免嵌套锁
// ❌ 嵌套锁容易死锁
synchronized (lock1) {
synchronized (lock2) {
// ...
}
}
// ✅ 尽量单一锁
synchronized (lock) {
// 一次性完成操作
}
5. 使用并发容器
// ❌ 手动同步
Map<String, String> map = new HashMap<>();
synchronized (map) {
map.put("key", "value");
}
// ✅ 使用并发容器
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
map.put("key", "value"); // 内部已优化,避免死锁
6. 开放调用(Open Call)
// ❌ 在持有锁时调用外部方法
synchronized (lock) {
externalMethod(); // 可能导致死锁
}
// ✅ 开放调用
Object snapshot;
synchronized (lock) {
snapshot = getState();
}
externalMethod(snapshot); // 锁外调用
死锁 vs 活锁 vs 饥饿
死锁(Deadlock)
// 线程互相等待,都无法继续
活锁(Livelock)
// 线程不断重试,但都无法成功
public class LivelockDemo {
static class Spoon {
private Diner owner;
public synchronized void use() {
System.out.printf("%s正在用勺子吃饭%n", owner.name);
}
public synchronized void setOwner(Diner diner) {
owner = diner;
}
}
static class Diner {
private String name;
private boolean isHungry;
public void eatWith(Spoon spoon, Diner spouse) {
while (isHungry) {
// 如果勺子不是自己的
if (spoon.owner != this) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
continue; // 继续等待
}
// 如果配偶也饿了
if (spouse.isHungry) {
System.out.println(name + ": 亲爱的,你先吃吧");
spoon.setOwner(spouse); // 礼让
continue; // 活锁:两人不断礼让
}
// 吃饭
spoon.use();
isHungry = false;
}
}
}
}
饥饿(Starvation)
// 低优先级线程长期得不到执行
Thread lowPriority = new Thread(() -> {
// 永远得不到CPU时间
});
lowPriority.setPriority(Thread.MIN_PRIORITY);
| 问题 | 特征 | 解决方案 |
|---|---|---|
| 死锁 | 互相等待,永久阻塞 | 破坏死锁条件 |
| 活锁 | 不断重试,无法成功 | 引入随机性 |
| 饥饿 | 长期得不到资源 | 公平锁、优先级调整 |
答题总结
面试标准答案:
死锁定义
死锁是指多个线程互相持有对方需要的资源,导致所有线程都无法继续执行。
产生条件(四个必要条件)
- 互斥条件:资源只能被一个线程占用
- 请求与保持:持有资源的同时请求其他资源
- 不可剥夺:资源不能被强制释放
- 循环等待:存在线程-资源的循环等待链
检测方法
- jstack:命令行工具,打印线程堆栈
- JConsole:图形化工具,”检测死锁”按钮
- ThreadMXBean:编程检测,
findDeadlockedThreads()
解决方案
- 破坏请求与保持:一次性申请所有资源
- 破坏不可剥夺:使用
tryLock()超时机制 - 破坏循环等待:资源排序,统一获取顺序(推荐)
- 使用并发工具:Semaphore、ReadWriteLock
最佳实践
- 锁顺序一致:全局统一的加锁顺序
- 缩小锁范围:只锁必要的代码
- 避免嵌套锁:尽量单一锁
- 使用并发容器:ConcurrentHashMap等
- 超时机制:
tryLock(timeout) - 开放调用:锁外调用外部方法
核心记忆:死锁 = 相互等待,解决 = 破坏四个条件之一,最常用 = 资源排序。