死锁是在多线程处理任务中,比较有挑战性的问题。通常死锁的发生会随在服务不响应请求等其他情况。
在以下情况中可能会存在死锁:
- 交叉锁导致死锁: 线程A持有X1锁等待X2锁、线程B持有X2等待X1锁。
- 内存不足:当有两个线程,每一个线程都要获取30M内存来执行任务。线程A获取10M内存等待20M,线程B获取20M内存等待10M。
- 一问一答式的数据交互:当客户端与服务端需要基于双方的回复时,如果客户端将请求发送后等待服务端响应,当因为网络原因无法给到服务端,服务端进行等待客户端发生。
- 数据库锁:无论是数据库表级别的锁,还是行级别的锁,比如某一个线程执行for update语句退出了事务,其他线程访问该数据库是都将陷入死锁。
- 文件锁:某一个线程获得到文件锁意外提出,其他读取该文件的线程也将会进入死锁知道系统释放文件句柄资源。
- 死循环导致死锁:程序由于代码原因或者对某些一次处理不得当,进入了死循环。收入查询线程堆栈不会发现如何死锁现象,这个这种死锁一般称为系统假死,是一种最为致命的也是最难排查的死锁现象,进程对系统资源的使用量又达到了极限,想要做出dump也是比较困难。
死锁示例
/**
* 测试死锁线程
* 写一个基于交叉锁导致的死锁
*/
public class DeadLock {
private final static Object MUTEX_1 =new Object();
private final static Object MUTEX_2 =new Object();
public void method1(){
synchronized (MUTEX_1){
System.out.println("已经获取MUTEX_1的monitor,尝试获取MUTEX_2");
synchronized (MUTEX_2){
System.out.println("获取MUTEX_2的monitor成功,开始执行任务");
}
}
}
public void method2(){
synchronized (MUTEX_2){
System.out.println("已经获取MUTEX_2的monitor,尝试获取MUTEX_1");
synchronized (MUTEX_1){
System.out.println("获取MUTEX_1的monitor成功,开始执行任务");
}
}
}
public static void main(String[] args) {
DeadLock deadLock = new DeadLock();
new Thread(()->{
while (true){
//循环等待
deadLock.method1();
}
}).start();
new Thread(()->{
while (true){
//循环等待
deadLock.method2();
}
}).start();
}
}
死锁的诊断
我们大致知道死锁会在什么情况下发生后,我们可以借助一些工具来判断不同的情况下的死锁。
- jstack 检查线程运行状态
- jconsole 可视化线程状态
- jvisualvm
- jProfile
死锁的避免方案
- 支持定时的锁:打破构成死锁条件的循环等待、不可剥夺原因。
- 支持设置一个公平竞争对象,拿到该对象锁后就可以访问资源:打破构成死锁条件的资源互斥原因。