返回
Java多线程基础(7):死锁的本质与预防
后端
2023-11-06 13:55:03
在多线程编程中,死锁是一种令人头疼的难题,它会让线程无限期地等待下去,从而导致程序无法继续运行。要理解死锁,我们首先需要了解可重入锁的概念。
可重入锁
可重入锁是一种特殊类型的锁,它允许同一个线程多次获取同一个锁。也就是说,当一个线程已经持有锁时,它可以再次获取这个锁,而不会被阻塞。这与不可重入锁形成鲜明对比,后者不允许同一个线程多次获取同一个锁。
在多线程编程中,可重入锁通常用于保护共享资源,确保同一时间只有一个线程可以访问这些资源。例如,一个银行账户对象可能会使用一个可重入锁来保护其余额,防止多个线程同时修改余额。
死锁
死锁发生在多个线程相互等待的情况下,每个线程都持有另一个线程需要的锁。这会导致线程陷入无限等待,无法继续执行。
一个常见的死锁示例是两个线程试图获取两个不同的锁。第一个线程首先获取锁A,然后尝试获取锁B。与此同时,第二个线程首先获取锁B,然后尝试获取锁A。这样,两个线程就陷入了死锁,因为每个线程都等待另一个线程释放它所持有的锁。
预防死锁
预防死锁至关重要,因为它会导致程序崩溃。以下是一些常见的预防死锁的方法:
- 避免嵌套锁 :嵌套锁会导致死锁,因为一个线程可能会在持有外部锁时尝试获取内部锁。
- 使用定时锁 :为锁设置一个超时时间,如果线程在超时时间内无法获取锁,则释放锁。
- 使用死锁检测和恢复机制 :在应用程序中实现一种机制,可以检测并恢复死锁。
Java中的死锁检测和恢复
Java中提供了java.util.concurrent.locks.ReentrantLock 类,它实现了可重入锁。ReentrantLock 提供了以下方法来检测和恢复死锁:
- isLocked() :检查锁是否已被锁定。
- isHeldByCurrentThread() :检查锁是否由当前线程持有。
- getHoldCount() :返回当前线程持有的锁的重入次数。
- lockInterruptibly() :尝试获取锁,如果无法获取则抛出InterruptedException 异常。
使用这些方法,我们可以实现一种死锁检测和恢复机制,如下所示:
public class DeadlockDetector implements Runnable {
private ReentrantLock lock1;
private ReentrantLock lock2;
public DeadlockDetector(ReentrantLock lock1, ReentrantLock lock2) {
this.lock1 = lock1;
this.lock2 = lock2;
}
@Override
public void run() {
try {
// 尝试获取锁1,如果无法获取则抛出InterruptedException异常
lock1.lockInterruptibly();
// 尝试获取锁2,如果无法获取则抛出InterruptedException异常
lock2.lockInterruptibly();
} catch (InterruptedException e) {
// 检测到死锁,恢复程序
System.out.println("检测到死锁,恢复程序...");
} finally {
// 释放锁
lock1.unlock();
lock2.unlock();
}
}
}
当检测到死锁时,我们可以通过以下方式恢复程序:
- 中断死锁线程 :使用Thread.interrupt() 方法中断死锁线程,使其抛出InterruptedException 异常。
- 释放死锁锁 :使用ReentrantLock.unlock() 方法释放死锁线程持有的锁。
- 重新获取锁 :死锁线程恢复后,可以重新获取锁并继续执行。
通过使用死锁检测和恢复机制,我们可以有效地预防死锁,确保程序的稳定运行。