Java并发编程深度解析与实战
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人


1.11 快速定位并解决线程导致的生产问题

在使用多线程时,如果使用不当,就会产生一些问题,比如:

• 死锁导致请求无法响应。

• CPU占用率很高,响应很慢。

学完本章的内容之后,是否能够解决这些问题呢?

我们先来创建一个Spring Boot应用,模拟一个死锁问题和CPU占用率过高的问题,核心代码如下。

1.11.1 死锁导致请求无法响应

将上面的这段程序运行后,使用如下命令制造死锁场景:

对于死锁问题的排查,具体操作步骤如下:

第一步,通过jps命令,查看Java进程的pid。

第二步,通过jstack <pid>命令查看线程dump日志。

当发现死锁时,可以在打印的dump日志中找到Found one Java-level deadlock:信息,根据信息的内容分析问题出现的原因。

jstack是Java虚拟机自带的一种堆栈跟踪工具,它主要用于打印指定Java进程ID中当前时刻的线程dump日志,线程快照是当前Java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等,语法格式如下。

1.11.2 CPU占用率很高,响应很慢

为了演示CPU过高的场景,先执行如下命令,这个命令会让线程进入死循环状态。

上述命令运行完后,通过top -c命令可以动态显示进程及其占用资源的排行榜,从该结果中可以找到占用CPU最高的进程PID。

可以看到,CPU占用率100%的PID是80972,定位到该进程之后,我们再从线程的dump日志中去定位。

• 使用top -H -p 80972命令查找到该进程中消耗CPU最多的线程,从下面的打印结果中发现PID=81122的线程CPU占用率最高。

• 通过printf "0x%x\n" 81122命令把对应的线程PID转化为16进制进行打印,之所以要做这步操作是因为线程dump日志中是以16进制来显示线程PID的。

• 执行jstack命令,打印PID=80972进程的线程dump日志,然后通过管道命令grep从线程dump日志中查找到CPU占用率最高的线程。

-A表示显示匹配行及其后面的n行。

通过上述操作后,得到的线程dump信息如下,从该信息中我们发现,在WhileThread.run()方法中因为某个操作导致CPU占用率过高,于是基于这个信息我们可以进行分析从而解决该问题。

上述代码内容其实可以分为三个部分。

• 线程的基本信息:"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。

• 线程的堆栈信息,该信息可以用来快速定位具体的执行指令。