一、案例
代码是部署在linux环境中
package com.atguigu.cpu;
/**
* <pre>
* desc : jstack 死锁案例
* version : v1.0
* </pre>
*/
public class JstackDeadLockDemo {
/**
* 必须有两个可以被加锁的对象才能产生死锁,只有一个不会产生死锁问题
*/
private final Object obj1 = new Object();
private final Object obj2 = new Object();
public static void main(String[] args) {
new JstackDeadLockDemo().testDeadlock();
}
private void testDeadlock() {
Thread t1 = new Thread(() -> calLock_Obj1_First());
Thread t2 = new Thread(() -> calLock_Obj2_First());
t1.start();
t2.start();
}
/**
* 先synchronized obj1,再synchronized obj2
*/
private void calLock_Obj1_First() {
synchronized (obj1) {
sleep();
System.out.println("已经拿到obj1的对象锁,接下来等待obj2的对象锁");
synchronized (obj2) {
sleep();
}
}
}
/**
* 先synchronized obj2,再synchronized obj1
*/
private void calLock_Obj2_First() {
synchronized (obj2) {
sleep();
System.out.println("已经拿到obj2的对象锁,接下来等待obj1的对象锁");
synchronized (obj1) {
sleep();
}
}
}
/**
* 为了便于让两个线程分别锁住其中一个对象,
* 一个线程锁住obj1,然后一直等待obj2,
* 另一个线程锁住obj2,然后一直等待obj1,
* 然后就是一直等待,死锁产生
*/
private void sleep() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
二、问题呈现
按照国际惯例,我们把代码上传到linux系统进行执行,如果linux报错如下:
错误: 找不到或无法加载主类 JstackDeadLockDemo
解决方案:
原先的etc/profile中的classpath配置
export CLASSPATH=$JAVA_HOME/lib/tools.jar
改为:
export CLASSPATH=.:$JAVA_HOME/lib/tools.jar
加了个 .: 当前目录的意思
运行结果如下:
可以看到,程序依然处于运行状态。现在我们知道是线程死锁造成的问题。
三、问题分析
那么如果是生产环境的话,是怎么样才能发现目前程序有问题呢?我们可以推导一下,如果线程死锁,那么线程一直在占用CPU,这样就会导致CPU一直处于一个比较高的占用率。所示我们解决问题的思路应该是:
1、首先查看java进程ID
2、根据进程 ID 检查当前使用异常线程的pid
3、把线程pid变为16进制如 31695 -> 7bcf 然后得到0x7bcf
4、jstack 进程的pid | grep -A20 0x7bcf 得到相关进程的代码 (鉴于我们当前代码量比较小,线程也比较少,所以我们就把所有的信息全部导出来)
接下来是我们的实现上面逻辑的步骤,如下所示:
# 查看所有java进程 ID
jps -l
结果如下:
# 根据进程 ID 检查当前使用异常线程的pid
top -Hp 1456
结果如下:
从上图可以看出来,当前占用cpu比较高的线程 ID 是1465
接下来把 线程 PID 转换为16进制为
# 10 进制线程PId 转换为 16 进制
1465 ———-> 5b9
# 5b9 在计算机中显示为
0x5b9
最后我们把线程信息打印出来:
jstack 1456 > jstack.log
结果如下:
所有的准备工作已经完成,我们接下来分析日志中的信息,来定位问题出在哪里。
打开jstack.log文件 查找一下刚刚我们转换完的16进制ID是否存在(如果用的是vi 搜索的方式是/0x5b9)
jstack命令生成的thread dump信息包含了JVM中所有存活的线程,里面确实是存在我们定位到的线程 ID ,在thread dump中每个线程都有一个nid,在nid=0x5b9的线程调用栈中,我们发现两个线程在互相等待对方释放资源
到此就可以检查对应的代码是否有问题,也就定位到我们的死锁问题。
3.1-延伸大厂问题排查过程
1、首先查看java进程ID
2、根据进程 ID 检查当前使用异常线程的pid
3、把线程pid变为16进制如 31695 -> 7bcf 然后得到0x7bcf
4、jstack 进程的pid | grep -A20 0x7bcf 得到相关进程的代码 (鉴于我们当前代码量比较小,线程也比较少,所以我们就把所有的信息全部导出来)
5、ps aux | grep java 查看到当前java进程使用cpu、内存、磁盘的情况获取使用量异常的进程
6、top -Hp 进程pid 检查当前使用异常线程的pid
7、把线程pid变为16进制如 31695 - 》 7bcf 然后得到0x7bcf
8、jstack 进程的pid | grep -A20 0x7bcf 得到相关进程的代码
四、解决方案
(1)调整锁的顺序,保持一致
(2)或者采用定时锁,一段时间后,如果还不能获取到锁就释放自身持有的所有锁。