我们来看一个典型的死锁的例子
public class DeadLock implements Runnable{
private Object o1 = new Object();
private Object o2 = new Object();
private int i=0;
@Override
public void run() {
while(true) {
if(i%2==0) {
synchronized(o1) {
System.out.println("if o1");
synchronized(o2) {
System.out.println("if o2");
}
}
}
else {
synchronized(o2) {
System.out.println("else o2");
synchronized(o1) {
System.out.println("else o1");
}
}
}
i++;
}
}
}
public class DeadTest {
public static void main(String[] args) {
DeadLock dl = new DeadLock();
Thread t0 = new Thread(dl);
Thread t1 = new Thread(dl);
t0.start();
t1.start();
}
}
运行结果
可以看出程序进入了死循环,不再继续走下去了,这就是死锁问题!!!!
一、概念
指两个或两个以上的线程在执行过程中,由于竞争资源而造成的阻塞问题,若无外力作用下,他们将无法推进下去,此时系统处于死锁状态。
二、产生的原因
- 因竞争资源产生死锁
- 进程顺序推进不当发生死锁
三、出现死锁的必要条件
- 互斥条件:资源每次只能是一个线程使用 ==》资源
- 请求与保持条件:一个线程因请求资源而阻塞时,对以获取的资源保持不放 =》线程
- 不可剥夺条件:线程已获取的资源,在未使用之前不能强行剥夺
- 循环等待条件:若干的线程之间形成一种头尾相连接的循环等待资源关系
四、死锁的预防或解除
解决死锁的途径:预防、避免、检测和恢复
- 预防死锁:(破坏4个必要条件)
资源一次性分配:(破坏请求与保持条件)
可剥夺资源:在线程未满足条件时释放掉已占有的资源
资源有序分配:系统给每类资源赋予一个编号,每个线程按编号递增顺序请求资源,释放则相反。 - 避免死锁(银行家算法)
允许线程动态的申请资源,系统在资源分配之前先计算资源分配的安全性,若此次分配不会导致系统进入不安全状态,则给当前的线程分配该资源,否则进程等待。 - 检测与解除
当线程发现进入死锁了,立即从死锁状态解除掉,采用方式剥夺资源(从其他线程剥夺足够多的资源给死锁线程,以免除死锁状态的线程)
五、引起死锁的案例
1、生产者、消费者使用不当会产生死锁
wait、notify、notifyAll 使用不当就会产生死锁
2、多线程获取多把锁
有两把锁 lock1 和 lock2
Lock1.lock();
lock2.lock();
//todo
lock2.unlock();
lock1.unlock();
注意: 先加的锁应后释放,后加的锁应先释放,否则会引起死锁
3、哲学家就餐问题
圆桌上有5位哲学家、每两位中间有一个筷子;
每个哲学家有两件事要做:思考、吃饭(哲学家必须同时拿到两个筷子才能吃饭);
哲学家之间并不知道对方何时要吃饭、何时要思考,不能协商制定吃饭、思考策略;
制定一个拿筷子的策略,使得哲学家不会因为拿筷子而出现死锁乐观锁;
六、实际定位死锁问题的思路
- 首先需要确定java进程是否发生死锁
- 打开 jvisualvm工具,专门分析 JVMCPU,内存使用情况,以及线程的运行信息查看当前java进程各个线程运行的状态(颜色)
- 通过 jvisualvm 的线程 dump 或者 jstack 命令,把当前 java 进程所有线程的调用堆栈信息打印出来
- 分析main线程和子线程有没有关键短语:
- waiting for(资源地址)
- waiting to lock(资源地址)
- 看线程函数调用栈,定位到源码上,具体问题具体分析
根据开头的那个死锁代码为例:
部分堆栈信息:
还可参考该博客 死锁问题的出现和解决