![Java并发编程深度解析与实战](https://wfqqreader-1252317822.image.myqcloud.com/cover/971/43737971/b_43737971.jpg)
1.11 快速定位并解决线程导致的生产问题
在使用多线程时,如果使用不当,就会产生一些问题,比如:
• 死锁导致请求无法响应。
• CPU占用率很高,响应很慢。
学完本章的内容之后,是否能够解决这些问题呢?
我们先来创建一个Spring Boot应用,模拟一个死锁问题和CPU占用率过高的问题,核心代码如下。
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-051-1.jpg?sign=1738894284-x1KicI15OKZObFt0GRJMw9bKrzZty3vm-0-df5b5fc459568dc60c502bc27affec9a)
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-052-1.jpg?sign=1738894284-v9cHSNAs9mwGUGP5EXVaKFeZmpfjmf3Y-0-ad327e18b98f005877bbff9be074d13f)
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-053-1.jpg?sign=1738894284-isTDghacRuvt2i9YTcssVwn5sRP5ZTcD-0-8137cc93b16e7d0c256b168cfc1333bf)
1.11.1 死锁导致请求无法响应
将上面的这段程序运行后,使用如下命令制造死锁场景:
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-053-2.jpg?sign=1738894284-FpDitm2vqUDRVhzp2CrDdJ2OAFXVggJ7-0-26dcdb5f695f279932147fa33327c25d)
对于死锁问题的排查,具体操作步骤如下:
第一步,通过jps命令,查看Java进程的pid。
第二步,通过jstack <pid>命令查看线程dump日志。
当发现死锁时,可以在打印的dump日志中找到Found one Java-level deadlock:信息,根据信息的内容分析问题出现的原因。
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-053-3.jpg?sign=1738894284-IxcTBEPzQTfHfz2Z0cDSYZT7NAWpFDIW-0-635e01ac81e4c6eb18fabb1e6aa8798e)
jstack是Java虚拟机自带的一种堆栈跟踪工具,它主要用于打印指定Java进程ID中当前时刻的线程dump日志,线程快照是当前Java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等,语法格式如下。
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-054-1.jpg?sign=1738894284-VZkMo64RMq4SJJdoXLKWZyOQIlN7ZXaQ-0-4e06a9790ee0c07970ed6d70dbe8c8df)
1.11.2 CPU占用率很高,响应很慢
为了演示CPU过高的场景,先执行如下命令,这个命令会让线程进入死循环状态。
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-054-2.jpg?sign=1738894284-5wcgHUxDUxFY90DkLmRetP8SqSo2AdGz-0-6916ae34941abf0630f4a9edb9fc03c6)
上述命令运行完后,通过top -c命令可以动态显示进程及其占用资源的排行榜,从该结果中可以找到占用CPU最高的进程PID。
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-054-3.jpg?sign=1738894284-v3v5rpHwoxxTBTdYCPboTndTq8POLh7l-0-575a0d7ed8011ad2c72e47cd97553bea)
可以看到,CPU占用率100%的PID是80972,定位到该进程之后,我们再从线程的dump日志中去定位。
• 使用top -H -p 80972命令查找到该进程中消耗CPU最多的线程,从下面的打印结果中发现PID=81122的线程CPU占用率最高。
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-055-1.jpg?sign=1738894284-hz0NbbAReemy2mJsaGIzjC4fttadwbpT-0-2da239a68e6b04307265c7142c6883db)
• 通过printf "0x%x\n" 81122命令把对应的线程PID转化为16进制进行打印,之所以要做这步操作是因为线程dump日志中是以16进制来显示线程PID的。
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-055-2.jpg?sign=1738894284-dgnC8rn8rl5qQcAo4VVx2dgI7F50fYhp-0-6bbe79805325205462c2e3a4b90517a7)
• 执行jstack命令,打印PID=80972进程的线程dump日志,然后通过管道命令grep从线程dump日志中查找到CPU占用率最高的线程。
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-055-3.jpg?sign=1738894284-1PsmANG5E7myyFIiI7Kkz4KRdwWm6hjC-0-d6209a120a30ffd5071281594d3bee05)
-A表示显示匹配行及其后面的n行。
通过上述操作后,得到的线程dump信息如下,从该信息中我们发现,在WhileThread.run()方法中因为某个操作导致CPU占用率过高,于是基于这个信息我们可以进行分析从而解决该问题。
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-055-4.jpg?sign=1738894284-pCppUKEGAL04iXaFKLtLFIgK2gv9Yn3L-0-a5d3e3917ea86d317518b2850d495645)
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-056-1.jpg?sign=1738894284-pxl9YLgIIKSehIuBFR9hR7Tr1MsOtCqM-0-c5ef6b32a5107c782c91e6fc3510e11b)
上述代码内容其实可以分为三个部分。
• 线程的基本信息:"Thread-3" #30 daemon prio=5 os_prio=0 tid=0x00007f84500ce000 nid=0x13ce2 runnable [0x00007f84a78f7000]。
○ Thread-3表示线程名字,为了更好地辨别,建议大家在使用线程的时候自己进行命名。
○ #30,线程的编号。
○ daemon,表示守护线程。
○ prio,线程的优先级,Java中的线程优先级分为1~10个级别,数字越高表示优先级越高,优先级高的线程能够有更高的概率优先得到CPU的执行。
○ os_prio,表示操作系统层面的线程优先级,Java中配置的线程优先级最终会映射到操作系统中的线程优先级。
○ tid=0x00007f84500ce000,JVM内部线程ID。
○ nid,系统线程ID
○ runnable,线程状态。
• 线程的运行状态,java.lang.Thread.State: RUNNABLE。
• 线程的堆栈信息,该信息可以用来快速定位具体的执行指令。