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

1 篇文章 0 订阅
1 篇文章 0 订阅

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

  • 死锁导致请求无法响应
  • CPU占用率很高,响应很慢

先创建一个springboot应用,模拟一个死锁问题和cpu占用率过高的问题,核心代码如下:

/**
 * 模拟一个死锁问题和cpu占用率过高的问题
 */
@RestController
public class ThreadController
{
    @GetMapping("/loop")
    public String dumpWhile()
    {
        new Thread(new WhileThread()).start();
        return "OK";
    }

    @GetMapping("/dead")
    public String dumpDeadlock()
    {
        Thread a = new ThreadRunA();
        Thread b = new ThreadRunB();
        a.start();
        b.start();
        return "OK";
    }
}

class WhileThread implements Runnable{
    @Override
    public void run()
    {
        while (true)
        {
            System.out.println("Thread");
        }
    }
}

class ThreadRunA extends Thread{
    @Override
    public void run()
    {
        System.out.println("======================A====================");
        synchronized (A.A){
            System.out.println("我要开始执行任务A..."+Thread.currentThread().getName());
            try
            {
                Thread.sleep(5000);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }

            synchronized (B.B)
            {

            }

            System.out.println("我在执行任务结束了A..."+Thread.currentThread().getName()+":"+B.B.hashCode()+":"+A.A.hashCode());

        }
    }
}

class ThreadRunB extends Thread{
    @Override
    public void run()
    {
        System.out.println("======================B====================");
        synchronized (B.B){
            System.out.println("我要开始执行任务B..."+Thread.currentThread().getName());
            try
            {
                Thread.sleep(1000);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }

            synchronized (A.A)
            {

            }

            System.out.println("我在执行任务结束了B..."+Thread.currentThread().getName()+":"+B.B+":"+A.A);

        }
    }
}

死锁导致请求无法响应

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

curl http://127.0.0.1:8080/dead

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

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

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

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

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

CPU占用率过高,响应很慢

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

curl http://127.0.0.1:8080/loop

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

可以看到,CPU占用率比较高 的PID是21602,定位到该进程之后,我们再从线程的dump日志中去定位.

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

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

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

jstack 21602 | grep 0x7426 -A 20 // jstack pid(进程id) | grep 16进制线程 -A 行数

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

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

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

  • 线程的基本信息: "Thread-2”#29 daemon prio=5 os_prio=0 tid-0x00007f84500ce nid=0x13ce2 runnable[0x00007f84a78f7000].

。 Thread-2 表示线程名字,为了更好地辨别,建议大家在使用线程的时候自己进行命令.

。 #29,线程的编号。

。 daemon,表示守护线程。

。 prio,线程的优先级,Java 中的线程优先级分为 1~10 个级别,数字越高表示优先级高,优先级高的线程能够有更高的概率优先得 到 CPU 的执行。

。 os_prio,表示操作系统层面的线程优先级,Java 中配置的线程优先级最终会映射到操系统中的线程优先级。

。 tid=0x00007f84500ce000,JM 内部线程 ID

。 nid,系统线程ID

。 runnable,线程状态

  • 线程的运行状态,iava.lang.Thread.State: RUNNABLE.
  • 线程的堆栈信息,该信息可以用来快速定位具体的执行命令.
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值