Java 多线程(三)—— 死锁
在多线程编程中,死锁是一个常见且令人头疼的问题。死锁发生在两个或多个线程相互等待对方持有的资源,导致所有线程都无法继续执行。为了更好地理解死锁,我们首先来看看什么是死锁,以及如何在 Java 中避免死锁。
死锁的产生
死锁通常发生在以下四个条件同时满足时:
- 互斥条件:至少有一个资源必须被一个线程独占。
- 保持与等待:一个线程持有至少一个资源,并等待获取其他资源。
- 不剥夺条件:线程已经获得的资源在完成之前不能被剥夺。
- 循环等待条件:存在一个线程的循环等待资源的情况。
代码示例
下面的代码演示了一个简单的死锁场景。在这个例子中,我们有两个线程,线程 A 和线程 B,它们分别持有两个锁 lock1
和 lock2
。
public class DeadlockExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread threadA = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread A: Holding lock 1...");
// 睡眠一段时间以确保线程 B 获取 lock 2
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("Thread A: Waiting for lock 2...");
synchronized (lock2) {
System.out.println("Thread A: Acquired lock 2!");
}
}
});
Thread threadB = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread B: Holding lock 2...");
// 睡眠一段时间以确保线程 A 获取 lock 1
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("Thread B: Waiting for lock 1...");
synchronized (lock1) {
System.out.println("Thread B: Acquired lock 1!");
}
}
});
threadA.start();
threadB.start();
}
}
在以上代码中,threadA
先获取 lock1
,然后尝试获取 lock2
;而 threadB
则先获取 lock2
,然后尝试获取 lock1
。由于两者在相互等待对方持有的锁,程序最终会陷入死锁状态,两个线程将永远互相等待下去。
如何避免死锁
- ** Lock Ordering(锁顺序)**:确保所有线程按照相同的顺序请求锁。比如,始终先获取
lock1
,再获取lock2
,这样可以避免循环等待的情况。
public class AvoidDeadlock {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread threadA = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread A: Holding lock 1...");
synchronized (lock2) {
System.out.println("Thread A: Acquired lock 2!");
}
}
});
Thread threadB = new Thread(() -> {
synchronized (lock1) { // 先获取 lock1
System.out.println("Thread B: Holding lock 1...");
synchronized (lock2) { // 然后获取 lock2
System.out.println("Thread B: Acquired lock 2!");
}
}
});
threadA.start();
threadB.start();
}
}
-
使用时间限制:为锁的请求设置超时机制,如果无法在规定时间内获取锁,则放弃请求,从而减少死锁的可能性。
-
避免持有锁的时间过长:尽量减少锁的持有时间,确保在持有锁时尽快完成操作,并释放锁。
-
使用更高层次的抽象:使用 Java 提供的并发工具类,如
ReentrantLock
,Semaphore
等,它们通常会提供更好的死锁控制策略。
结论
死锁是多线程编程中的一个重要问题,可以通过设计良好的锁策略、合理的顺序和超时机制来有效避免。理解死锁的发生机制和预防措施,对于提高 Java 并发程序的稳定性和性能至关重要。希望通过本篇文章的讲解,能帮助开发者更深入地理解死锁及其解决方案。