![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=1739188615-9T9hf6ZE5SnH8S2P68q24LuboPT32dno-0-088b38d5e646d8ee7b5e67939dda4e33)
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-052-1.jpg?sign=1739188615-tXIwpTGiZOf8fuxRMRnLCkhKaqZXN4lq-0-00d04c39d74e5b68503f626bd36526a7)
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-053-1.jpg?sign=1739188615-2Ei8cZ5oIVk8aFugXZ33gLYOZ105v8pS-0-0a3b498b8c79278eae1d07da9fa8f991)
1.11.1 死锁导致请求无法响应
将上面的这段程序运行后,使用如下命令制造死锁场景:
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-053-2.jpg?sign=1739188615-hMISdAFKUfJYPgcndFRhfJb5hGvv3rPN-0-d8d528ace1d20213e05ad0ca79e10973)
对于死锁问题的排查,具体操作步骤如下:
第一步,通过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=1739188615-zCtK0FLOFKqq1j7rtX8Kk6JUZIMIkPvj-0-98624c7928ba250a7631022bd67601ce)
jstack是Java虚拟机自带的一种堆栈跟踪工具,它主要用于打印指定Java进程ID中当前时刻的线程dump日志,线程快照是当前Java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等,语法格式如下。
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-054-1.jpg?sign=1739188615-eic8RPck4joPorGpem5T7GXaWLdk3Wek-0-0b1a0d4b1932883bf70e4422249b6029)
1.11.2 CPU占用率很高,响应很慢
为了演示CPU过高的场景,先执行如下命令,这个命令会让线程进入死循环状态。
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-054-2.jpg?sign=1739188615-VEvxLq6XqAGAoi44XKnI6wViw6HIJqyN-0-34c2c6c9d691e0eee69c40e79dbd1b84)
上述命令运行完后,通过top -c命令可以动态显示进程及其占用资源的排行榜,从该结果中可以找到占用CPU最高的进程PID。
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-054-3.jpg?sign=1739188615-LgCxEQSRHrQRNsgt56rezoDtJITLW41B-0-5c24d26f626453fb6b7d6b9f2ae17e9c)
可以看到,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=1739188615-ArJ66bBsUJy8rqYFy26JH2GLh87I4rlD-0-1033cfeb55202e720af4f953fdbb62be)
• 通过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=1739188615-ccWwzKaESTyUkXr0DYoKMfRChXGAcAzQ-0-ddf47507bcbd7dda84c700f719274fd0)
• 执行jstack命令,打印PID=80972进程的线程dump日志,然后通过管道命令grep从线程dump日志中查找到CPU占用率最高的线程。
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-055-3.jpg?sign=1739188615-UlxMXgpUxqFK5aYRC3Qe6q90jS2Xtv5i-0-ac9472e29c31fef08f124163087a5e6c)
-A表示显示匹配行及其后面的n行。
通过上述操作后,得到的线程dump信息如下,从该信息中我们发现,在WhileThread.run()方法中因为某个操作导致CPU占用率过高,于是基于这个信息我们可以进行分析从而解决该问题。
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-055-4.jpg?sign=1739188615-MJ5LgoZOdyElLjktsX3FMOWllcnjsbim-0-7d419e47d8c397539a8418aa1a96c2e6)
![](https://epubservercos.yuewen.com/11B439/23020636309729206/epubprivate/OEBPS/Images/42136-00-056-1.jpg?sign=1739188615-3ndS0CS6Jo1ZTcf1EYDwHFpmnaZzWEuy-0-eca385a6ee7cb9b44cc95b2f14f6ef83)
上述代码内容其实可以分为三个部分。
• 线程的基本信息:"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。
• 线程的堆栈信息,该信息可以用来快速定位具体的执行指令。