目录:
- 什么是死锁?
- 死锁是怎么产生的?
- 怎么排查死锁?
- 死锁的预防
- 拓展:Java CPU 100%排查
一 什么是死锁?
注:线程和进程都可能会产生死锁,以下以线程为例
死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,
若无外力作用,它们都将无法推进下去。
此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的线程称为死锁线程。
另一种定义:
集合中的每个线程都在等待只能由本集合内的其他线程才能引发的事件,那么该组线程是死锁的。
竞争的资源可以是:锁、网络连接、通知事件,磁盘、带宽,以及一切可以被称作“资源”的东西。
二 死锁是怎么产生的?
死锁的主要产生原因:
- 系统资源不足
- 进程/线程运行的顺序不当
- 资源分配不当
产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个线程使用
- 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:线程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相连的循环等待资源关系
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会产生死锁
常见死锁:
避免死锁的一个通用的经验法则是
当多个线程都要访问资源A、B、C时,保证使每个线程都按照同样的顺序去访问它们,
即:让所有线程按照同样的顺序获得一组锁;
三、怎么排查死锁
首先,编写一个死锁程序:
public static void main(String[] args) {
final Object a = new Object();
final Object b = new Object();
Thread threadA = new Thread(new Runnable() {
public void run() {
synchronized (a) {
try {
System.out.println("now i in threadA-locka");
Thread.sleep(1000l);
synchronized (b) {
System.out.println("now i in threadA-lockb");
}
} catch (Exception e) {
// ignore
}
}
}
});
Thread threadB = new Thread(new Runnable() {
public void run() {
synchronized (b) {
try {
System.out.println("now i in threadB-lockb");
Thread.sleep(1000l);
synchronized (a) {
System.out.println("now i in threadB-locka");
}
} catch (Exception e) {
// ignore
}
}
}
});
threadA.start();
threadB.start();
}
上面的代码执行后,就会出现死锁,排查方法如下:
方式1:使用jps + jstack
第一:在windons命令窗口,使用 jps -l
第二:在windons命令窗口,使用 jstack -l 9004
死锁信息如上图,结合代码看就可以找出原因了。
方式2:使用jconsole
在window打开 JConsole,JConsole是一个图形化的监控工具
四、死锁预防:
遇见问题,解决问题是一般人的思维。高手都是提前预见问题,并有效预防。
死锁的常见预防思路如下:
- 以确定的顺序获得锁
- 超时放弃
当使用synchronized关键字提供的内置锁时,只要线程没有获得锁,就会永远等待下去。
然而Lock接口提供了:
boolean tryLock(long time, TimeUnit unit) throws InterruptedException
该方法可以按照固定时间等待锁,所以线程可以在获取锁超时时,主动释放之前已经获得的所有的锁。
五、拓展:
Java CPU 100%排查