解释死锁的概念,并给出一个死锁发生的例子。
参考回答
死锁是指两个或多个线程在运行过程中,因竞争资源而相互等待,导致线程永远无法继续执行的状态。换句话说,死锁发生时,线程 A 等待线程 B 持有的资源,而线程 B 同时等待线程 A 持有的资源,造成循环等待。
死锁的四个必要条件(”不可缺少条件”):
- 互斥条件:某些资源(如锁)在某一时刻只能由一个线程占用。
- 占有并等待:一个线程占有资源的同时,还在等待其他线程占有的资源。
- 不可抢占:资源不能被强制抢占,只能由线程自己释放。
- 循环等待:多个线程之间形成一个环状的资源等待关系。
详细讲解与拓展
死锁的例子
代码示例:
class Resource {
private final String name;
public Resource(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class DeadlockExample {
public static void main(String[] args) {
Resource resourceA = new Resource("ResourceA");
Resource resourceB = new Resource("ResourceB");
Thread thread1 = new Thread(() -> {
synchronized (resourceA) {
System.out.println(Thread.currentThread().getName() + " locked " + resourceA.getName());
try {
Thread.sleep(100); // 模拟一些工作
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resourceB) {
System.out.println(Thread.currentThread().getName() + " locked " + resourceB.getName());
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (resourceB) {
System.out.println(Thread.currentThread().getName() + " locked " + resourceB.getName());
try {
Thread.sleep(100); // 模拟一些工作
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resourceA) {
System.out.println(Thread.currentThread().getName() + " locked " + resourceA.getName());
}
}
});
thread1.start();
thread2.start();
}
}
可能输出:
Thread-0 locked ResourceA
Thread-1 locked ResourceB
解释:
- 线程 1 持有
resourceA
,试图获取resourceB
的锁。 - 线程 2 持有
resourceB
,试图获取resourceA
的锁。 - 由于两个线程都在等待对方释放锁,形成了死锁,程序无法继续执行。
如何检测和避免死锁?
1. 避免死锁的策略
- 避免循环等待条件: 为所有线程获取锁的顺序规定一个一致的顺序。按固定顺序获取资源可以有效避免死锁。
synchronized (resourceA) { synchronized (resourceB) { // 按固定顺序锁定资源 } }
- 尝试锁定超时机制: 使用
tryLock()
方法,可以避免长时间等待锁。import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class DeadlockAvoidance { private static final Lock lock1 = new ReentrantLock(); private static final Lock lock2 = new ReentrantLock(); public static void main(String[] args) { Thread thread1 = new Thread(() -> { try { if (lock1.tryLock()) { try { Thread.sleep(100); if (lock2.tryLock()) { try { System.out.println("Thread 1: Locked both resources"); } finally { lock2.unlock(); } } } finally { lock1.unlock(); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); Thread thread2 = new Thread(() -> { try { if (lock2.tryLock()) { try { Thread.sleep(100); if (lock1.tryLock()) { try { System.out.println("Thread 2: Locked both resources"); } finally { lock1.unlock(); } } } finally { lock2.unlock(); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); thread1.start(); thread2.start(); } }
- 减少锁的粒度: 尽可能减少锁定的资源和范围,降低锁冲突的概率。
2. 检测死锁
使用 jstack
工具来检测 Java 应用程序中是否存在死锁。
步骤:
- 获取运行 Java 程序的进程 ID(
jps
命令)。 - 使用
jstack <PID>
分析线程堆栈信息。 - 检查是否有
Found one Java-level deadlock
的信息。
总结:死锁的四个必要条件
条件 | 描述 |
---|---|
互斥条件 | 每个资源在同一时刻只能被一个线程占用。 |
占有并等待 | 一个线程已持有一个资源,但还在等待其他线程持有的资源。 |
不可抢占 | 资源不能被强制剥夺,只能由占有它的线程主动释放。 |
循环等待 | 线程间形成一个环状的等待资源链,每个线程都在等待下一个线程持有的资源。 |