死锁是多线程编程中的一个重要概念。当两个或更多的线程无法继续执行,因为每个线程都在等待另一个线程释放资源时,就会发生死锁。这可能导致应用程序挂起或崩溃,因此对死锁的理解以及如何避免死锁是非常重要的。
死锁的必要条件:
互斥条件:一个资源每次只能被一个线程使用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:已经分配的资源,未使用完之前不能强行剥夺。
环路等待条件:系统中若干个进程形成一种头尾相接的环路,每个进程都在等待下一个进程所占有的资源。
如果这些条件同时满足,系统就可能发生死锁。
死锁的例子(银行家算法)
下面是一个简单的Java代码示例,演示了如何产生死锁。这个例子使用了银行家算法,这是一种避免死锁的算法:
public class DeadlockExample {
private static final int N = 5; // 线程数量
private static final int M = 10; // 资源数量
private static int[] allocation = new int[N]; // 分配给每个线程的资源数量
private static int[] maximum = new int[N]; // 每个线程最大需求量
private static int[] available = new int[M]; // 系统可用资源数量
private static int count = 0; // 记录死锁次数
public static void main(String[] args) {
// 初始化数据
for (int i = 0; i < N; i++) {
maximum[i] = 2; // 每个线程最大需求2个资源
allocation[i] = 0; // 初始分配资源数量为0
}
for (int i = 0; i < M; i++) {
available[i] = 2; // 系统初始可用资源数量为2
}
// 创建线程并执行,尝试获取资源,如果成功则进行后续操作,否则等待
for (int i = 0; i < N; i++) {
new Thread(new Worker(i)).start();
}
}
static class Worker implements Runnable {
private int id;
public Worker(int id) { this.id = id; }
public void run() {
while (true) {
synchronized (this) {
if (available[0] > 0) { // 如果可用资源大于0,尝试获取资源
available[0]--; // 减少可用资源数量
try { wait(); } catch (InterruptedException e) {} // 等待其他线程释放资源
available[0]++; // 增加可用资源数量,以便其他线程可以获取资源
} else { // 如果可用资源为0,则死锁发生,打印相关信息并退出循环
count++; // 增加死锁次数计数器
System.out.println("Deadlock #" + count + " occurred!");
break; // 退出循环,等待下一次重启线程
}
}
}
}
}
}
在这个例子中,每个线程都会尝试获取一个资源(在这个例子中是available[0]),如果成功则进行后续操作(在这个例子中是等待),如果失败则尝试等待其他线程释放资源。当可用资源为0时,即没有资源可以分配给任何一个线程时,就会发生死锁。此时程序会打印出死锁发生的次数,并退出循环,等待下一次重启线程。