死锁产生的核心原因:线程之间互相持有对方线程所等待的资源,但是一直又等不到。
1 死锁的原因分析:
1.1、交叉锁
如:两个线程持有了对方向下执行所需的资源;
如:一根独木桥,两个人分别从两端走上来,如果谁都不让谁先过,持有了过桥的部分资源,最后导致的结果就是两个人都一直卡在桥上,这个现象在程序中就称之为“死锁”;
1.2、内存不够
在系统内存不足的情况下,两个线程同时执行某个任务,分别获取了50M内存,但是执行任务最小需要70M内存,此时两个线程都会等待着内存的释放;
1.3、同步问答
这个可以理解为业务级别的死锁,服务端和客户端之间交互,涉及到数据状态的转换,因为某种原因导致请求中断或者直接没收到请求,如果没有做重试机制的话,此时状态就不会变化而导致业务一直在等待;
1.4、悲观锁
数据库的select for update语句,锁表、锁行;(会造成交叉锁)
1.5、文件锁
线程获得了文件的句柄资源,但是却意外退出了,此时他还享有对文件的访问权,而其他线程若要访问该资源需要等待文件句柄资源的释放;
1.6、死循环
由于代码原因导致程序进入了死循环,导致CPU的占有率居高不下,导致系统假死,其他线程获取不了CPU的资源分配;
2 分析死锁和解决死锁问题
2.1 交叉死锁代码
package com.michael.demo;
public class CrossLockTest {
private static final Object LOCK_1 = new Object();
private static final Object LOCK_2 = new Object();
private static long SLEEP_TIME = 30 * 1000;
private void testLock1() {
//首先synchronized是可重入锁,一个线程得到"锁A"后允许该线程再次请求该对象锁,"锁A",这就是synchronized的可重入性。
synchronized (CrossLockTest.LOCK_1) {//
System.out.println(Thread.currentThread().getName() + "获取了锁: LOCK_1");
try {
Thread.sleep(SLEEP_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "休息" + (SLEEP_TIME / 1000f) + "s之后,尝试获取锁: LOCK_2");
synchronized (CrossLockTest.LOCK_2) {
System.out.println(Thread.currentThread().getName() + "获取了锁: LOCK_2");
try {
Thread.sleep(SLEEP_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private void testLock2() {
synchronized (CrossLockTest.LOCK_2) {
System.out.println(Thread.currentThread().getName() + "获取了锁: LOCK_2");
try {
Thread.sleep(SLEEP_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "休息" + (SLEEP_TIME / 1000f) + "s之后,尝试获取锁: LOCK_1");
synchronized (CrossLockTest.LOCK_1) {
System.out.println(Thread.currentThread().getName() + "获取了锁: LOCK_1");
try {
Thread.sleep(SLEEP_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
CrossLockTest test = new CrossLockTest();
new Thread(test::testLock1).start();
new Thread(test::testLock2).start();
}
}
两个线程中执行的方法,锁1和锁2的持有是反过来的,在sleep之后形成一个互相等待的效果,大家如果觉着效果不明显,可以在main()里面的调用线程中加入死循环,让线程一直等待的效果更好理解。
代码的运行结果如下:
可以看到两个线程在互相等待,期望获得对方所持有的锁。
2.2 jdk自带jconsole工具分析原因
到此,工具很明确的指出了死锁的两个线程,如果您已定位问题所在,那下面的文章你可以不用看了,若不能,还能进一步分析:
2.3 jdk自带jstack工具分析原因
jvm对于每个启动的线程都会分配一个stack栈,此时可以使用jstack工具:
// jstack.exe -l 进程号
D:\dev\Java\jdk1.8.0_131\bin>jstack.exe -l 8736
看到了吧 交叉锁!!!^_^