一、死锁是如何产生的?
死锁:是指两个或多个线程在执行过程中,因争夺资源而造成的一种僵局。具体来说,每个线程持有一部分资源,并等待其他线程所持有的资源释放,导致所有线程都无法继续执行。
例如:
线程A 获得 lockX 对象锁,接下来想获取 lockY对象的锁;
线程B 获得 lockY 对象锁,接下来想获取 lockX对象的锁。
代码如下:
public class DeadlockTest {
private static Object lockX = new Object();
private static Object lockY = new Object();
public static void main(String[] args) {
Thread threadA = new Thread(() -> {
synchronized (lockX) {
System.out.println("Thread A: Holding lock X...");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Thread A: Waiting for lock Y...");
synchronized (lockY) {
System.out.println("Thread A: Holding lock X and lock Y...");
}
}
}, "threadA"); // 线程起别名
Thread threadB = new Thread(() -> {
synchronized (lockY) {
System.out.println("Thread B: Holding lock Y...");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("Thread B: Waiting for lock X...");
synchronized (lockX) {
System.out.println("Thread B: Holding lock Y and lock X...");
}
}
}, "threadB");
threadA.start();
threadB.start();
}
}
控制台输出结果:
此时程序并没有结束,这种现象就是死锁现象...线程A持有lock X锁等待获取lock Y锁,线程B持有lock Y锁等待获取lock X锁。
二、如何进行死锁诊断?
当程序出现了死锁现象,我们可以使用jdk自带的工具:jps和 jstack
步骤如下:
第一:查看运行的线程
打开终端,运行命令:jps,得到Java进程的PID、主类的完整名称。
第二:使用jstack查看线程运行的情况
运行命令:jstack -l 18348
发现一个死锁:
三、如何避免死锁?
预防死锁
预防死锁的策略是破坏死锁的四个必要条件之一:
- 互斥条件:确保资源可以被同时访问,不过并非所有资源都能够做到这一点。
- 请求与保持条件:一次性申请所有资源,不允许分步申请。
- 不可剥夺条件:一旦资源被分配给某线程,就不应该从该线程中强制夺回。
- 循环等待条件:对资源实施一个全局的顺序,确保所有线程按顺序申请资源。
避免死锁
避免死锁的策略是在运行时避免系统进入不安全状态。银行家算法是一个著名的避免死锁的算法,它通过动态分析资源分配来确保系统始终处于安全状态。
四、解决死锁示例:
在上面的例子中,如果线程A和线程B几乎同时开始执行,它们可能会相互等待对方释放锁,从而导致死锁。
解决这个问题的一种方法是确保所有线程按相同的顺序获取锁:
public class DeadlockResolvedDemo {
private static Object lockX = new Object();
private static Object lockY = new Object();
public static void main(String[] args) {
Thread threadA = new Thread(() -> {
synchronized (lockX) {
System.out.println("Thread A: Holding lock X...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("Thread A: Waiting for lock Y...");
synchronized (lockY) {
System.out.println("Thread A: Holding lock X and lock Y...");
}
}
});
Thread threadB = new Thread(() -> {
synchronized (lockX) { // 注意这里也是先获取lockX
System.out.println("Thread B: Holding lock X...");
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("Thread B: Waiting for lock Y...");
synchronized (lockY) {
System.out.println("Thread B: Holding lock X and lock Y...");
}
}
});
threadA.start();
threadB.start();
}
}
在这个修改后的例子中,无论线程A还是线程B,都是先尝试获取lockX
,然后再获取lockY
,这样就打破了循环等待条件,从而避免了死锁。