死锁
# 死锁
# 什么是死锁
死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象。当线程A持有资源1并等待资源2,而线程B持有资源2并等待资源1时,就会发生死锁。
# 死锁的四个必要条件
- 互斥条件:资源不能被共享,一次只能被一个线程使用
- 请求与保持条件:线程已经保持了至少一个资源,但又提出了新的资源请求
- 不剥夺条件:线程获得的资源在使用完之前不能被其他线程强行剥夺
- 循环等待条件:若干线程之间形成头尾相接的循环等待资源关系
# 死锁的预防
# 1. 破坏互斥条件
- 允许资源共享使用,例如使用读写锁
# 2. 破坏请求与保持条件
- 一次性申请所有需要的资源
- 只有在所有资源都可用时才分配
# 3. 破坏不剥夺条件
- 当线程申请新资源失败时,主动释放已持有的资源
- 允许其他线程抢占已分配的资源
# 4. 破坏循环等待条件
- 对资源进行编号,要求线程按顺序申请资源
- 使用资源分配图算法检测并避免循环等待
# 死锁的避免
使用银行家算法等资源分配策略,在分配资源前进行安全性检查,确保系统不会进入不安全状态。
# 死锁的检测与恢复
# 检测方法
- 资源分配图算法
- 银行家算法
# 恢复策略
- 终止所有死锁进程
- 按优先级终止部分死锁进程
- 资源剥夺:从死锁进程中剥夺资源
# 实际案例分析
# 案例1:哲学家就餐问题
# 问题描述
5个哲学家围坐在一张圆桌旁,每两个哲学家之间有一根筷子,每个哲学家需要两根筷子才能吃饭。如果每个哲学家都先拿起左边的筷子,再尝试拿起右边的筷子,就会发生死锁。
# 解决方案
- 最多允许4个哲学家同时拿起左边的筷子
- 按顺序获取筷子(奇数号先拿左边,偶数号先拿右边)
- 使用信号量控制资源访问
# 案例2:线程池中的死锁
# 问题描述
线程池中的线程A等待线程B的结果,而线程B又等待线程A的结果,形成死锁。
# 解决方案
- 避免线程间的循环依赖
- 使用超时机制,避免无限等待
- 合理设计任务分解,减少线程间的依赖
# 代码示例
# Java中的死锁示例
public class DeadlockExample {
private static final Object resource1 = new Object();
private static final Object resource2 = new Object();
public static void main(String[] args) {
// 线程1:先获取resource1,再获取resource2
Thread thread1 = new Thread(() -> {
synchronized (resource1) {
System.out.println("Thread 1: Holding resource 1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Waiting for resource 2");
synchronized (resource2) {
System.out.println("Thread 1: Holding resource 1 and 2");
}
}
});
// 线程2:先获取resource2,再获取resource1
Thread thread2 = new Thread(() -> {
synchronized (resource2) {
System.out.println("Thread 2: Holding resource 2");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2: Waiting for resource 1");
synchronized (resource1) {
System.out.println("Thread 2: Holding resource 1 and 2");
}
}
});
thread1.start();
thread2.start();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 避免死锁的代码示例
public class DeadlockAvoidance {
private static final Object resource1 = new Object();
private static final Object resource2 = new Object();
public static void main(String[] args) {
// 线程1:按顺序获取资源
Thread thread1 = new Thread(() -> {
synchronized (resource1) {
System.out.println("Thread 1: Holding resource 1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Waiting for resource 2");
synchronized (resource2) {
System.out.println("Thread 1: Holding resource 1 and 2");
}
}
});
// 线程2:按相同顺序获取资源
Thread thread2 = new Thread(() -> {
synchronized (resource1) {
System.out.println("Thread 2: Holding resource 1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2: Waiting for resource 2");
synchronized (resource2) {
System.out.println("Thread 2: Holding resource 1 and 2");
}
}
});
thread1.start();
thread2.start();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 总结
死锁是并发编程中的常见问题,了解死锁的产生原因和解决方法对于编写可靠的并发程序至关重要。通过破坏死锁的四个必要条件、使用死锁避免算法、以及合理的资源分配策略,可以有效减少死锁的发生。
在实际开发中,应注意以下几点:
- 尽量减少资源的竞争
- 按顺序申请资源
- 使用超时机制避免无限等待
- 定期检测死锁并采取恢复措施
通过合理的设计和编码实践,可以显著提高系统的可靠性和性能。
编辑 (opens new window)