【JVM】- 使用jstack排查线程异常问题

问题发现

我们从一道面试题来开始进行分析。

题目内容为: 要求使用2个线程顺序输出:A1B2C3D4E5F6G7

以下为该题的简单实现:

package com.ssj.thread;
class ThreadPrint{
    public static void main(String[] args) {
       // 保证顺序 用来保证num线程需要等待 char线程state为0的时候才能执行
       // cdl 每次countDown()的时候 state都会减去1, 
       // countDown方法采用cas方式保证了state操作的线程安全性
       CountDownLatch cdl = new CountDownLatch(1);

       String charStr = "ABCDEFG";
       String numStr = "1234567";

       Object lock = new Object();

       new Thread(() -> {
           try {
               //  若是num线程先抢到执行资源 则先等待char线程输出第一个字母
               // 这个必须要写在sync-lock外面保证不让num线程抢到lock
               cdl.await();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           synchronized (lock) {
               for (char c : numStr.toCharArray()) {
                   try {
                       // 唤醒其它线程
                       lock.notify();
                       System.out.print(c);
                       // 释放锁并进入等待
                       lock.wait();
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }
               // 输出完后打印一个换行
               System.out.println();
           }
       }, "num").start();

       new Thread(() -> {
           synchronized (lock) {
               // 抢到执行资源之后 执行countDown更改cdl的状态 
               // 并告知在此锁上等待的线程
               // 这个必须要写在sync-lock里面  保证让char线程抢到lock
               cdl.countDown();
               for (char c : charStr.toCharArray()) {
                   System.out.print(c);
                   try {
                       // 唤醒其它线程
                       lock.notify();
                       // 释放锁并进入等待
                       lock.wait();
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }
           }

       }, "char").start();
    }
}

我们编译并运行该方法

$ javac ThreadPrint.java
$ java -classpath ../../../ com.ssj.thread.ThreadPrint

运行结果如下:
在这里插入图片描述

由图所示,可以看出程序在运行结束后并没有退出,而是一直挂在那里,此时我们就需要jstack的帮助啦

排查过程

  1. 首先我们先找到对应的进程号
$ jps 

786 DemoOne
808 Jps
  1. 查看该进程里面的线程信息
$ top -Hp 786

此处省略...
  804 sunsj     20   0 6542012  26900  12580 S  0.0  0.2   0:00.00 Service Thread
  805 sunsj     20   0 6542012  26900  12580 S  0.0  0.2   0:00.03 VM Periodic Tas
  807 sunsj     20   0 6542012  26900  12580 S  0.0  0.2   0:00.00 num
  1. 根据信息可以看出我们的num线程此刻还在运行,线程PID为807,由于jstack的nid是16进制的,所以我们将线程号转为16进制
$ printf "%x\n" 807
327
  1. 找出对应堆栈信息
$ jstack 786 | grep "327" -A 10

在这里插入图片描述

可以看出,该线程处于WAITING状态,分析代码可知,num最后输出7的时候执行了lock.wait()而此时char线程的循环已经执行完了却没有调用notify方法唤起在lock上等待的线程,导致了num线程一直处于等待状态.

由此分析,我们在char循环执行完之后再唤醒一次num试试看该方法能否正常退出
更改char线程的内容如下

new Thread(() -> {
   synchronized (lock) {
       // 抢到执行资源之后 执行countDown更改cdl的状态 
       // 并告知在此锁上等待的线程
       // 这个必须要写在sync-lock里面  保证让char线程抢到lock
       cdl.countDown();
       for (char c : charStr.toCharArray()) {
           System.out.print(c);
           try {
               // 唤醒其它线程
               lock.notify();
               // 释放锁并进入等待
               lock.wait();
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
       // 此处添加一个notify
       lock.notity()
   }

}, "char").start();

再次执行

$ javac ThreadPrint.java
$ java -classpath ../../../ com.ssj.thread.ThreadPrint

在这里插入图片描述

由图所示,程序可正常退出。至此修复完成!O(∩_∩)O

我们可以使用以下方法循环执行10000次查看效果

public static void printThread() {

       // 保证本方法同步
        CountDownLatch cdlFunc = new CountDownLatch(2);


        // 保证顺序 用来保证num线程需要等待 char线程state为0的时候才能执行
        // cdl 每次countDown()的时候 state都会减去1, countDown方法采用cas方式保证了state操作的线程安全性
        CountDownLatch cdl = new CountDownLatch(1);


        String charStr = "ABCDEFG";
        String numStr = "1234567";


        Object lock = new Object();


        new Thread(() -> {
            synchronized (lock) {
                // 抢到lock之后 countDown cdl的状态 并通知num线程再去获取lock
                // 而此时num线程已经获取不到了 只能等待char线程的nofity
                cdl.countDown();
                for (char c : charStr.toCharArray()) {
                    System.out.print(c);
                    try {
                        // 唤醒其它线程
                        lock.notify();
                        // 释放锁并进入等待
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                lock.notify();
            }
            cdlFunc.countDown();
        }, "char").start();

    new Thread(() -> {
        try {
            // 若是num现场先抢到执行资源 则先等待让char线程去获取锁
            if(cdl.getCount() > 0){
                 cdl.await();
             }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (lock) {
            for (char c : numStr.toCharArray()) {
                try {
                    // 唤醒其它线程
                    lock.notify();
                    System.out.print(c);
                    // 释放锁并进入等待
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 输出完后打印一个换行
            System.out.println();

        }
        cdlFunc.countDown();
    }, "num").start();

    try {
        cdlFunc.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

}

推荐一个jstack文件非常好用的的分析工具:https://gceasy.io/

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

生如夏花般绚丽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值