
`
第九章 程序死锁
1、交叉锁可导致程序出现死锁
线程A持有R1的锁等待获取R2的锁,线程B持有R2的锁等待获取R1的锁,这种情况最容易导致死锁问题。
2、内存不足
当并发请求系统可用内存时,如果此时系统内存不足,则可能会出现死锁的情况。举个例子,两个线程T1和T2,执行某个任务,其中T1已经获取了10MB内存,T2获取了20MB内存,如果每个线程的执行单元都需要30MB的内存,但是剩余可用的内存刚好是20MB,那么两个线程有可能都在等待彼此能够释放内存资源。
3、一问一答式的数据交换
服务端开启某个端口,等待客户端访问,客户端发送请求立即等待接收,由于某种原因服务端错过了客户端的请求,仍然在等待一问一答式的数据交换,此时服务端和客户端都在等待着双方发送数据。
4、数据库锁
无论是数据库表级别的锁,还是行级别的锁,比如某个线程执行for update语句退出了事务,其他线程访问该数据库时都将陷入死锁。
5、死循环引起的死锁
程序由于代码原因或者对某些异常处理不得当,进入死循环,虽然查看线程堆栈信息不会发现任何死锁迹象,但是程序不工作,cpu占有率又居高不下,这种死锁一般称为系统假死,是一种最为致命也是最难排查的死锁现象,由于重现困难,进程对系统资源的使用量又达到了极限,想要做出dump有时候也是非常困难的。
我们将举例说明程序由于交叉锁引起的死锁现象,交叉锁不仅是指自己写的代码出现了交叉的情况,如果使用某个框架或者开源库,由于对源码API的不熟悉,很有可能也会引起死锁。示例代码如下:
public class DeadLockTest {
public static void main(String[] args) {
DeadLock deadLock = new DeadLock();
OtherService otherService = new OtherService();
deadLock.setOtherService(otherService);
otherService.setDeadLock(deadLock);
new Thread(() -> {
while (true) {
deadLock.m1();
}
}, "T1").start();
new Thread(() -> {
while (true) {
otherService.s2();
}
}, "T2").start();
}
}
public class DeadLock {
private OtherService otherService;
public void setOtherService(OtherService otherService) {
this.otherService = otherService;
}
// DeadLock的实例的锁-资源A
private final Object LOCK = new Object();
public void m1() {
synchronized (LOCK) {
System.out.println("********m1********");
otherService.s1();
}
}
public void m2() {
synchronized (LOCK) {
System.out.println("********m2********");
}
}
}
public class OtherService {
private DeadLock deadLock;
public void setDeadLock(DeadLock deadLock) {
this.deadLock = deadLock;
}
// OtherService的实例的锁-资源B
private final Object LOCK = new Object();
public void s1() {
synchronized (LOCK) {
System.out.println("========s1========");
}
}
public void s2() {
synchronized (LOCK) {
System.out.println("========s2========");
deadLock.m2();
}
}
}
上面的程序可以看出程序有死锁的风险。
大致知道了引起死锁的几种原因之后,本节,我们将借助诊断工具对其进行诊断。
1、交叉锁引起的死锁
运行DeadLockTest 代码,程序将陷入死锁,打开jstack工具或者jconsole工具,Jstack-l PID会直接发现死锁的信息
这里不贴出。
一般交叉锁引起的死锁线程都会进入BLOCKED状态,cpu资源占用不高,很容易借助工具来发现。
2、死循环引起的死锁(假死)
使用jstack,jconsole、jvisualvm工具或者jprofile工具进行诊断,但是不会给出明显的提示,因为工作的线程并未BLOCKED,而是始终处于RUNNABLE状态,cpu使用率高居不下,甚至不能够正常运行你的命令。
严格意义上来说死循环会导致程序假死,算不上真正的死锁,但是某个仙鹤草呢个对cpu消耗过多,导致其他线程等待cpu,内存等资源也会陷入死锁等待。