死锁
概念
死锁是指两个或多个以上的进程在执行过程中,因争夺资源而造成一种互相等待的现象,若无外力干涉那他们都将无法推进下去。如果资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。
如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
下面通过一个代码来实现上图的情况以模拟死锁:
import java.util.concurrent.TimeUnit;
/**
* 资源类
*/
class HoldResourceThread implements Runnable{
private String resource1;
private String resource2;
public HoldResourceThread(String resource1, String resource2) {
this.resource1 = resource1;
this.resource2 = resource2;
}
@Override // 持有自己的资源,还想得到别人的资源
public void run() {
synchronized (resource1) {
System.out.println(Thread.currentThread().getName() + "\t 自己持有" + resource1 + "\t 尝试获取:" + resource2);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resource2) {
System.out.println(Thread.currentThread().getName() + "\t get" + resource2);
}
}
}
}
public class DeadLockDemo {
public static void main(String[] args) {
String resource1 = "resource1";
String resource2 = "resource2";
new Thread(new HoldResourceThread(resource1, resource2), "A").start();
new Thread(new HoldResourceThread(resource2, resource1), "B").start();
}
}
运行结果如下,main线程无法结束
A 自己持有resource1 尝试获取:resource2
B 自己持有resource2 尝试获取:resource1
线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后让线程 A 休眠 2s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁
上面的例子符合产生死锁的四个必要条件:
- 互斥条件:该资源任意时刻只由一个线程占用
- 请求于保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只能自己使用完毕后才释放资源
- 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系
预防和避免死锁
如何预防死锁? 破坏死锁的产生的必要条件即可:
- 破坏请求与保持条件 :一次性申请所有的资源
- 破坏不剥夺条件 :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源
- 破坏循环等待条件 :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件
如何避免死锁?避免死锁就是在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态。
安全状态 指的是系统能够按照某种线程推进顺序(P1、P2、P3…..Pn)来为每个线程分配所需资源,直到满足每个线程对资源的最大需求,使每个线程都可顺利完成。称
<P1、P2、P3.....Pn>序列为安全序列
排查死锁
当我们出现死锁的时候,首先需要使用 jps 命令查看运行的程序
jps -l
我们可以看到 DeadLockDemo 这个类,一直在运行
再使用 jstack 查看堆栈信息,后面参数是 jps 输出该类的 pid
jstack 23504
输出结果
通过查看最后一行,我们看到 Found 1 deadlock,即存在一个死锁
Java内存区域
这里推荐看这篇文章: Java 内存区域详解(重点)