死锁(Deadlock)是多线程编程中常见的问题,指的是两个或多个线程互相持有对方所需的资源,导致所有线程都无法继续执行的情况。死锁的发生需要满足以下四个必要条件(称为死锁的四个条件):
1. 死锁的四个条件
- 互斥条件(Mutual Exclusion):
- 资源一次只能被一个线程占用。
- 占有并等待(Hold and Wait):
- 线程持有至少一个资源,并等待获取其他被占用的资源。
- 非抢占条件(No Preemption):
- 线程持有的资源不能被其他线程强行抢占,只能由线程主动释放。
- 循环等待条件(Circular Wait):
- 存在一个线程等待的循环链,每个线程都在等待下一个线程所持有的资源。
只有当这四个条件同时满足时,死锁才会发生。
2. 死锁的示例
以下是一个典型的死锁示例:
public class DeadlockExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1: Holding lock 1...");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Waiting for lock 2...");
synchronized (lock2) {
System.out.println("Thread 1: Acquired lock 2!");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread 2: Holding lock 2...");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2: Waiting for lock 1...");
synchronized (lock1) {
System.out.println("Thread 2: Acquired lock 1!");
}
}
});
thread1.start();
thread2.start();
}
}
在这个示例中:
Thread 1
持有lock1
,并等待lock2
。Thread 2
持有lock2
,并等待lock1
。- 两个线程互相等待,导致死锁。
3. 如何避免死锁
为了避免死锁,可以尝试破坏死锁的四个必要条件中的至少一个。以下是常见的避免死锁的策略:
(1)破坏占有并等待条件
- 一次性申请所有需要的资源,而不是逐个申请。
- 如果无法一次性获取所有资源,则释放已持有的资源并重试。
示例:
synchronized (lock1) {
synchronized (lock2) {
// 一次性获取所有锁
}
}
(2)破坏非抢占条件
- 允许线程抢占其他线程持有的资源。
- 使用
Lock
类(如ReentrantLock
)的tryLock()
方法,尝试获取锁,如果失败则释放已持有的锁。
示例:
Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();
if (lock1.tryLock()) {
try {
if (lock2.tryLock()) {
try {
// 成功获取所有锁
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
(3)破坏循环等待条件
- 对资源进行排序,要求线程按照固定的顺序申请资源。
- 例如,所有线程必须先申请
lock1
,再申请lock2
。
示例:
public void method1() {
synchronized (lock1) {
synchronized (lock2) {
// 按照固定顺序获取锁
}
}
}
public void method2() {
synchronized (lock1) {
synchronized (lock2) {
// 按照固定顺序获取锁
}
}
}
(4)使用超时机制
- 在获取锁时设置超时时间,如果超时则释放已持有的锁并重试。
- 可以使用
Lock
类的tryLock(long time, TimeUnit unit)
方法。
示例:
if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
// 成功获取所有锁
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
(5)检测和恢复
- 使用工具或算法检测死锁,并在检测到死锁时强制释放资源。
- 例如,Java 的
ThreadMXBean
可以检测死锁。
示例:
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long[] threadIds = bean.findDeadlockedThreads();
if (threadIds != null) {
// 处理死锁
}
4. 总结
- 死锁的发生需要满足四个必要条件:互斥条件、占有并等待、非抢占条件和循环等待条件。
- 避免死锁的策略包括:
- 破坏占有并等待条件。
- 破坏非抢占条件。
- 破坏循环等待条件。
- 使用超时机制。
- 检测和恢复死锁。
- 在实际开发中,应尽量避免嵌套锁,并按照固定顺序获取锁。
通过合理的设计和编码,可以有效避免死锁问题。
THE END
暂无评论内容