一、案例

代码是部署在linux环境中

  1. package com.atguigu.cpu;
  2. /**
  3. * <pre>
  4. * desc : jstack 死锁案例
  5. * version : v1.0
  6. * </pre>
  7. */
  8. public class JstackDeadLockDemo {
  9. /**
  10. * 必须有两个可以被加锁的对象才能产生死锁,只有一个不会产生死锁问题
  11. */
  12. private final Object obj1 = new Object();
  13. private final Object obj2 = new Object();
  14. public static void main(String[] args) {
  15. new JstackDeadLockDemo().testDeadlock();
  16. }
  17. private void testDeadlock() {
  18. Thread t1 = new Thread(() -> calLock_Obj1_First());
  19. Thread t2 = new Thread(() -> calLock_Obj2_First());
  20. t1.start();
  21. t2.start();
  22. }
  23. /**
  24. * 先synchronized obj1,再synchronized obj2
  25. */
  26. private void calLock_Obj1_First() {
  27. synchronized (obj1) {
  28. sleep();
  29. System.out.println("已经拿到obj1的对象锁,接下来等待obj2的对象锁");
  30. synchronized (obj2) {
  31. sleep();
  32. }
  33. }
  34. }
  35. /**
  36. * 先synchronized obj2,再synchronized obj1
  37. */
  38. private void calLock_Obj2_First() {
  39. synchronized (obj2) {
  40. sleep();
  41. System.out.println("已经拿到obj2的对象锁,接下来等待obj1的对象锁");
  42. synchronized (obj1) {
  43. sleep();
  44. }
  45. }
  46. }
  47. /**
  48. * 为了便于让两个线程分别锁住其中一个对象,
  49. * 一个线程锁住obj1,然后一直等待obj2,
  50. * 另一个线程锁住obj2,然后一直等待obj1,
  51. * 然后就是一直等待,死锁产生
  52. */
  53. private void sleep() {
  54. try {
  55. Thread.sleep(100);
  56. } catch (InterruptedException e) {
  57. e.printStackTrace();
  58. }
  59. }
  60. }

二、问题呈现

按照国际惯例,我们把代码上传到linux系统进行执行,如果linux报错如下:
错误: 找不到或无法加载主类 JstackDeadLockDemo


解决方案:

原先的etc/profile中的classpath配置
export CLASSPATH=$JAVA_HOME/lib/tools.jar

改为:
export CLASSPATH=.:$JAVA_HOME/lib/tools.jar

加了个 .: 当前目录的意思

运行结果如下:
image.png

可以看到,程序依然处于运行状态。现在我们知道是线程死锁造成的问题。

三、问题分析

那么如果是生产环境的话,是怎么样才能发现目前程序有问题呢?我们可以推导一下,如果线程死锁,那么线程一直在占用CPU,这样就会导致CPU一直处于一个比较高的占用率。所示我们解决问题的思路应该是:

1、首先查看java进程ID
2、根据进程 ID 检查当前使用异常线程的pid
3、把线程pid变为16进制如 31695 -> 7bcf 然后得到0x7bcf
4、jstack 进程的pid | grep -A20 0x7bcf 得到相关进程的代码 (鉴于我们当前代码量比较小,线程也比较少,所以我们就把所有的信息全部导出来)

接下来是我们的实现上面逻辑的步骤,如下所示:
# 查看所有java进程 ID
jps -l

结果如下:
image.png
# 根据进程 ID 检查当前使用异常线程的pid
top -Hp 1456

结果如下:
image.png
从上图可以看出来,当前占用cpu比较高的线程 ID 是1465

接下来把 线程 PID 转换为16进制为
# 10 进制线程PId 转换为 16 进制
1465 ———-> 5b9
# 5b9 在计算机中显示为
0x5b9
image.png

最后我们把线程信息打印出来:
jstack 1456 > jstack.log

结果如下:
image.png

所有的准备工作已经完成,我们接下来分析日志中的信息,来定位问题出在哪里。

打开jstack.log文件 查找一下刚刚我们转换完的16进制ID是否存在(如果用的是vi 搜索的方式是/0x5b9)
image.png

jstack命令生成的thread dump信息包含了JVM中所有存活的线程,里面确实是存在我们定位到的线程 ID ,在thread dump中每个线程都有一个nid,在nid=0x5b9的线程调用栈中,我们发现两个线程在互相等待对方释放资源
image.png
到此就可以检查对应的代码是否有问题,也就定位到我们的死锁问题。

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)或者采用定时锁,一段时间后,如果还不能获取到锁就释放自身持有的所有锁。