栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【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 "%xn" 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线程去获取锁
            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();
    }

}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/677945.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号