死锁(Deadlock)是多线程编程中一个常见的问题,指的是两个或多个线程在互相等待对方释放资源的情况下,导致所有涉及的线程都无法继续执行的状态。死锁发生时,系统中的线程会陷入一种僵局,无法自行解脱,必须通过外部干预或重启系统来解决。
死锁的四个必要条件
死锁的发生必须同时满足以下四个条件,这四个条件被称为Coffman条件:
-
互斥条件(Mutual Exclusion):
- 至少有一个资源必须处于非共享模式,即一次只能被一个线程占用。
-
占有并等待条件(Hold and Wait):
- 一个线程必须占有至少一个资源,并等待获取其他线程占有的资源。
-
不可抢占条件(No Preemption):
- 资源不能被强制从占有它的线程中抢占,只能由占有它的线程主动释放。
-
循环等待条件(Circular Wait):
- 存在一组线程,其中每个线程都在等待下一个线程占有的资源,形成一个闭环。
死锁的示例
以下是一个简单的Java示例,展示了死锁的发生:
public class DeadlockExample {
private static final Object resource1 = new Object();
private static final Object resource2 = new Object();
public static void main(String[] args) {
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...");
}
}
});
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 2 and 1...");
}
}
});
thread1.start();
thread2.start();
}
}
在这个示例中,thread1
和thread2
分别占有了resource1
和resource2
,并试图获取对方占有的资源,从而形成了一个循环等待,导致死锁的发生。
如何避免死锁
-
破坏互斥条件:
- 尽量避免使用非共享资源,但这在实际应用中很难实现。
-
破坏占有并等待条件:
- 要求线程在开始执行前一次性获取所有需要的资源,或者使用资源申请超时机制。
-
破坏不可抢占条件:
- 允许资源被强制抢占,但这可能会导致数据不一致的问题。
-
破坏循环等待条件:
- 对资源进行排序,要求线程按照相同的顺序申请资源。
示例:避免死锁
以下是一个避免死锁的示例,通过资源排序来破坏循环等待条件:
public class DeadlockAvoidanceExample {
private static final Object resource1 = new Object();
private static final Object resource2 = new Object();
public static void main(String[] args) {
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...");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (resource1) { // 先获取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();
}
}
在这个示例中,thread2
先获取resource1
,再获取resource2
,避免了循环等待,从而避免了死锁的发生。
总结
死锁是多线程编程中一个常见且棘手的问题,通过理解死锁的四个必要条件和采取相应的预防措施,可以有效地避免死锁的发生。在实际应用中,应尽量遵循资源排序、一次性获取所有资源等原则,以确保系统的稳定性和可靠性。