死锁是指两个或多个线程(或进程)互相等待对方释放资源,导致所有相关线程都无法继续执行的情况。这是并发编程中的一个常见问题,可能会导致系统部分或完全停止响应。
死锁的产生条件
死锁的发生必须同时满足以下四个条件(也称为Coffman条件):
-
互斥条件(Mutual Exclusion):
- 至少有一个资源必须处于非共享模式,即一次只能被一个线程使用。
-
占有并等待条件(Hold and Wait):
- 一个线程必须占有至少一个资源,并同时等待获取其他线程持有的额外资源。
-
不可抢占条件(No Preemption):
- 资源只能由持有它们的线程主动释放,不能被其他线程强制剥夺。
-
循环等待条件(Circular Wait):
- 必须存在一个封闭的线程链,其中每个线程都在等待链中下一个线程所持有的资源。
死锁示例
考虑以下场景:
public class DeadlockExample {
private static Object resource1 = new Object();
private static 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) {}
System.out.println("Thread 1: Waiting for resource 2...");
synchronized (resource2) {
System.out.println("Thread 1: Holding resource 1 and resource 2...");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (resource2) {
System.out.println("Thread 2: Holding resource 2...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("Thread 2: Waiting for resource 1...");
synchronized (resource1) {
System.out.println("Thread 2: Holding resource 2 and resource 1...");
}
}
});
thread1.start();
thread2.start();
}
}
在这个例子中:
- 线程1先获取资源1,然后尝试获取资源2。
- 线程2先获取资源2,然后尝试获取资源1。
- 如果两个线程的执行时间恰好重叠,就会导致死锁。
预防死锁
预防死锁的方法主要包括:
- 破坏互斥条件:使用可以同时被多个线程访问的资源。
- 破坏占有并等待条件:一次性申请所有需要的资源。
- 破坏不可抢占条件:允许资源被强制剥夺。
- 破坏循环等待条件:按照固定的顺序申请资源。
在实际编程中,常用的方法包括使用超时机制、使用tryLock()方法、按照固定顺序获取锁等。理解死锁的产生条件和预防方法对于开发健壮的并发程序至关重要。