结合jvisualvm一次性看清线程状态

作者: pq217 | 来源:发表于2022-04-12 12:19 被阅读0次

前言

本文主要结合jvisualvm工具和thread自带的getState方法,分析不同情况下的线程状态
其中jvisualvm区分的线程状态区分如下


jvisualvm线程状态

jvm的线程状态区分如下

public enum State {
    NEW, // 新建的
    RUNNABLE, // 可运行的
    BLOCKED, // 阻塞的,等待在synchronized上的线程
    WAITING, // 等待被其它线程唤醒,通过Object#wait(),Thread#join(),LockSupport#park()都会进入这个状态
    TIMED_WAITING, // 等待固定时间,通过Object#wait(long),Thread#join(long),LockSupport#parkNanos,LockSupport#parkUntil会进入这个状态
    TERMINATED; // 结束
}

二者并非一一对应,区分的角度有点差别

sleep

先来最简单常用的sleep方法,顾名思义,就是让线程睡一会并指定醒来时间,并且释放cpu资源

public class SleepTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                TimeUnit.MINUTES.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "th-1");
        thread.start();
        TimeUnit.SECONDS.sleep(5);
        System.out.println("线程的状态:" + thread.getState());
    }
}

由于sleep是等待固定时间,所以其状态输出:TIMED_WAITING
在jvisualvm中sleep对应的状态是:休眠

休眠
使用sleep需要处理一个checked异常InterruptedException,这个接下来说

interrupt

interrupt字面意思上讲就是打断当前的干的事,但是没有实际上的作用,只是修改了线程的一个状态,换句话说,如果被打断的线程不理会,打断等于无效
我们用代码模拟一个家长喊在外面玩耍的熊孩子吃饭的故事

public class InterruptTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("孩子正在玩...");
            String game = "好玩";
            while ("好玩".equals(game)) {

            }
            System.out.println("孩子回家吃饭: " + Thread.currentThread().isInterrupted());
        }, "son");
        thread.start();
        TimeUnit.SECONDS.sleep(3);
        System.out.println("孩子的状态:" + thread.getState());
        System.out.println("家长喊孩子回家吃饭");
        thread.interrupt();
        while (true) {}
    }
}

代码输出:

孩子正在玩...
孩子的状态:RUNNABLE
家长喊孩子回家吃饭

孩子在外面玩,家长试图通过interrupt打断孩子并叫孩子回家吃饭,但孩子还是该干嘛干嘛,没有执行“回家吃饭”的代码

那么interrupt有什么用呐?大部分情况是结合isInterruptedinterrupted使用,二者可以查看线程是否被打断,也就是说interrupt相当于发送一个打断信号,isInterrupted可以获取打断信号,至于收到打断信号后如何处理用户代码自己决定

模拟一个听话的孩子,看到了打断信号,就开始放弃玩耍回家吃饭

public class IsInterruptedTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("孩子正在玩...");
            while (!Thread.currentThread().isInterrupted()) { // 时刻查看自己是否被打断

            }
            System.out.println("孩子回家吃饭: " + Thread.currentThread().isInterrupted());
        }, "son");
        thread.start();
        TimeUnit.SECONDS.sleep(3);
        System.out.println("孩子的状态:" + thread.getState());
        System.out.println("家长喊孩子回家吃饭");
        thread.interrupt();
    }
}

输出:

孩子正在玩...
孩子的状态:RUNNABLE
家长喊孩子回家吃饭
孩子回家吃饭: true

上面用的isInterrupted,也可以使用Thread.interrupted(),后者处理返回是否打断,还会清除打断信号,上面的循环代码修改为

while (!Thread.interrupted()) {

}
System.out.println("孩子回家吃饭: " + Thread.currentThread().isInterrupted());

结果只有最后一行有变化

孩子回家吃饭: false

变为false是因为执行Thread.interrupted()把打断信号清除了

上文讲到的sleep()方法默认响应打断,而响应的方式就是抛出InterruptedException异常,这就是为什么我们使用sleep()一定要处理InterruptedException异常

下面模拟一个孩子在外面睡觉的场景

public class InterruptedExceptionTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("孩子在外面睡觉...");
            try {
                TimeUnit.SECONDS.sleep(60);
            } catch (InterruptedException e) {
                System.out.println("孩子回家吃饭: " + Thread.currentThread().isInterrupted());
            }
        }, "son");
        thread.start();
        TimeUnit.SECONDS.sleep(3);
        System.out.println("孩子的状态:" + thread.getState());
        System.out.println("家长喊孩子回家吃饭");
        thread.interrupt();
        while (true) {}
    }
}

输出

孩子在外面睡觉...
孩子的状态:TIMED_WAITING
家长喊孩子回家吃饭
孩子回家吃饭: false

最后一条出现false,说明sleep也会先擦除打断信息再抛出异常

最后一个睡觉的案例孩子的状态是TIMED_WAITING上面已经介绍过,其余孩子正在玩的例子中孩子的状态都是RUNNABLE,这个状态代表线程是可运行的,至于是否真实正在运行,是操作系统的cpu调度决定的,再jvisualvm中,状态就是运行

运行

join

join的意思是等待某线程结束,那基本上对应的状态就是WAITING没跑了,试一下

模拟一个家长等孩子睡醒吃饭

public class JoinTest {
    public static void main(String[] args) throws InterruptedException {
        Thread pThread = Thread.currentThread();
        Thread thread = new Thread(() -> {
            System.out.println("孩子正睡觉...");
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("孩子醒来看家长状态:" + pThread.getState());
            System.out.println("孩子洗脸刷牙...");
            int i = 0;
            while (++i>0) {
                new Object();
                // 模拟刷牙过程
            }
            System.out.println("孩子起来了");
        }, "son");
        thread.start();
        System.out.println("家长等孩子醒...");
        thread.join();
        System.out.println("开始吃饭");
    }
}

输出

家长等孩子醒...
孩子正睡觉...
孩子醒来看家长状态:WAITING
孩子洗脸刷牙...
孩子起来了
开始吃饭

不出意料,孩子醒来看家长状态是WAITING,再看一下jvisualvm

Image.png

join过程jvisualvm的显示是黄色:等待

join也响应interrupte,和sleep一样抛出异常InterruptedException

synchronized

这个作用就不多说了,模拟一下两个线程运行同一个synchronized修饰的方法

public class SyncTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            doRun();
        }, "th-1");
        thread1.start();
        Thread thread2 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            doRun();
        }, "th-2");
        thread2.start();
        TimeUnit.SECONDS.sleep(3);
        System.out.println("th-2的状态:" + thread2.getState());
        thread2.interrupt(); // 尝试打断
        TimeUnit.SECONDS.sleep(3);
        System.out.println("th-2的状态:" + thread2.getState());
    }

    public synchronized static void doRun() {
        while (true){}
    } // 模拟执行时间很长
}

输出

th-2的状态:BLOCKED
th-2的状态:BLOCKED

即在synchronized上等待锁的线程的状态是BLOCKED而且synchronized不响应interrupte,而jvisualvm中,这种状态是粉色的监视

监视

ReentrantLock

接下来看同样是锁工具的ReentrantLock,修改上面代码使用ReentrantLock

public class LockTest {
    public static void main(String[] args) throws InterruptedException {
        Lock lock = new ReentrantLock();
        Thread thread1 = new Thread(() -> {
            lock.lock(); // 拿到锁不释放
        }, "th-1");
        thread1.start();
        Thread thread2 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.lock();
        }, "th-2");
        thread2.start();
        TimeUnit.SECONDS.sleep(3);
        System.out.println("th-2的状态:" + thread2.getState());
        thread2.interrupt();
        TimeUnit.SECONDS.sleep(3);
        System.out.println("th-2的状态:" + thread2.getState());
    }
}

输出

th-2的状态:WAITING
th-2的状态:WAITING

可以看到等待lock的线程状态是WAITING,和上面的join一样,再看jvisualvm

驻留
对应的是橙黄色的驻留,可以在jvm中join和lock以及后面讲的wait都是WAITING,而在jvisualvm中细分了一下lock这种等待叫做驻留

上面的第二条输出证明等待在lock()上的线程也不响应interrupt

实际上ReentrantLock内部是使用Unsafe.park方法让线程进行等待的

Unsafe.park

也是一种让线程等待的方法,并需要其他线程通过unpark唤醒,测试一下

家长喊休息的孩子起来吃饭

public class ParkTest {
    public static void main(String[] args) throws Exception {
        // 获得unsafe
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        // 开始
        Thread thread = new Thread(() -> {
            System.out.println("孩子正在休息");
            unsafe.park(false, 0);
            System.out.println("孩子被唤醒: " + Thread.currentThread().isInterrupted());
        }, "son");
        thread.start();
        TimeUnit.SECONDS.sleep(3);
        System.out.println("孩子的状态:" + thread.getState());
        System.out.println("家长喊孩子起来吃饭");
//        thread.interrupt();
        unsafe.unpark(thread);
    }
}

输出

孩子正在休息
孩子的状态:WAITING
家长喊孩子起来吃饭
孩子被唤醒: false

结果必然是WAITING,jvisualvm肯定和lock一样:驻留(lock内部使用就是unsafe.park)

驻留

unsafe.unpark(thread)改成thread.interrupt()试试打断,输出

孩子正在休息
孩子的状态:WAITING
家长喊孩子起来吃饭
孩子被唤醒: true

可以看到unsafe.park响应interrupt,效果和unpark一样,且不清除打断标记

但问题来了为啥使用park的lock不响应interrupt?原因是lock的内部做了逻辑处理

wait&notify

上面讲的join时线程处于等待状态,其实按字面意思等待状态最具代表性的应该是wait()方法,但由于它相对复杂点,放在最后说
首先wait和sleep、join都不一样,后二者是属于Thread对象的方法,而wait以及notify是任何对象都有的方法(即Object的方法)
wait 方法是释放对象锁并让当前线程等待在该对象上,直到另一个线程调用了该对象的 notify 或 notifyAll 方法之后,才能继续恢复执行
wait&notify必须先获得对象锁才能调用(必须在synchronized下),也就是说只有获得对象的锁才有资格在对象上等待,也只有获得锁才有资格通知在对象上等待的线程

下面用代码模拟一个孩子等家长做蛋堡的场景

public class WaitTest {
    public static Object o = new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            synchronized (o) {
                System.out.println("孩子正等待...");
                try {
                    o.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("孩子开始吃蛋堡");
            }
        }, "son");
        thread.start();
        TimeUnit.SECONDS.sleep(1);
        synchronized (o) {
            System.out.println("家长做蛋堡...");
            TimeUnit.SECONDS.sleep(3);
            System.out.println("孩子的状态:" + thread.getState());
            System.out.println("家长做完喊孩子吃");
            o.notifyAll();
//            System.out.println("收拾桌子");
//            TimeUnit.SECONDS.sleep(4);
//            System.out.println("孩子的状态:" + thread.getState());
        }
        while (true) {}
    }
}

输出

孩子正等待...
家长做蛋堡...
孩子的状态:WAITING
家长做完喊孩子吃
孩子开始吃蛋堡

以上代码o是一个空对象用来实现锁,它好比一个盘子,孩子和家长的一个约定做完蛋堡就放入盘子,首先孩子获得盘子,调用wait在盘子旁边等待,并交出盘子的控制权(调用wait会释放锁,收到notify时再重新尝试获得锁并继续执行代码)
家长获得盘子,并开始做蛋堡,做完之后通过notifyAll通知盘子旁边等待的孩子来吃
孩子调用wait对应的状态是WAITING,jvisualvm就是黄色的等待

等待
有个小问题,wait&notify必须在synchronized下执行,wait时会释放锁,那么执行notifyAll也会立即释放锁吗,做个测试把上面代码的注释打开
synchronized (o) {
    System.out.println("家长做蛋堡...");
    TimeUnit.SECONDS.sleep(3);
    System.out.println("孩子的状态:" + thread.getState());
    System.out.println("家长做完喊孩子吃");
    o.notifyAll();
    System.out.println("收拾厨房");
    TimeUnit.SECONDS.sleep(3);
    System.out.println("孩子的状态:" + thread.getState());
}

输出

孩子正等待...
家长做蛋堡...
孩子的状态:WAITING
家长做完喊孩子吃
收拾厨房
孩子的状态:BLOCKED
孩子开始吃蛋堡

可以看到,家长做完喊孩子吃(调用notifyAll)后,孩子并没有马上开始吃,而是收拾家长完厨房后才开始吃,但孩子的状态由WAITING变为BLOCKED,证明收到notify消息是,孩子就已经不再wait了,而是尝试获取锁,也就是等在synchronized外,而上文讲到等待synchronized外的线程是BLOCKED状态,而最终家长的synchronized代码执行完毕才实际释放锁,孩子开始吃蛋堡,最后对照jvisualvm分析下整个过程

整个过程
所以notifyAll只是唤醒WAITING,但WAITING醒来之后还要再次获得锁,所以如果获取不到就要BLOCKED阻塞

wait同样响应interrupte,响应方式和join,sleep一样是抛出InterruptedException异常

总结

最后还是总结一下,虽然我觉得这东西靠背肯定没用

  • 正在运行的线程是RUNABLE状态,在jvisualvm中是绿色运行
  • wait()会使线程进入WAITING状态,在jvisualvm中是黄色等待
  • join()会使线程进入WAITING状态,在jvisualvm中是黄色等待
  • sleep()会使线程进入TIMED_WAITING状态,在jvisualvm中是蓝色休眠TIMED_WAITING是一种特殊的等待,其他的固定时间的等待都是TIMED_WAITING状态,比如wait(6)
  • unsafe.park会使线程进入WAITING状态,在jvisualvm中是橙色驻留
  • synchronized会使线程进入BLOCKED状态,在jvisualvm中是粉色监视
  • interrupt代表打断,但实际响应需要配合isInterrupted手动实现,以上的方法中wait/join/sleep默认响应抛出异常InterruptedException,unsafe.park响应结束等待,synchronized和ReentrantLock的等待过程不响应

相关文章

  • 结合jvisualvm一次性看清线程状态

    前言 本文主要结合jvisualvm工具和thread自带的getState方法,分析不同情况下的线程状态其中jv...

  • 线程主要方法

    线程方法,结合线程状态图一起看 sleep() 与 interrupt()sleep(long millis): ...

  • 有了这款可视化工具,Java 应用性能调优 so easy。。。

    JVisualVM 简介 案例分析 准备模拟内存泄漏样例 使用JVisualVM分析内存泄漏 JVisualVM ...

  • 读书有感

    读书就要思考,结合书中情境,结合自身状态,要看清本质。 书中有些观点可以不认同,但对于作者勤于思考,勇...

  • jvisualVM:JVM监控分析工具使用介绍

    概述:本文介绍关于GC、线程等JVM通用指标监控的一些常用方式,重点介绍jvisualvm工具使用。 1、远端服务...

  • JVM监控-JVisualVM

    JVisualVM连接 JVisualVM为JDK自带工具,在安装JDK后,在%JAVA_HOME%\bin目录下...

  • java多线程

    线程六种状态 New:尚未启动的线程的线程状态(new Thread) Runnable:可运行线程的线程状态,等...

  • java多线程基本概念(一)

    线程生命周期 说明线程工共包含5个状态: 新建状态new:调用线程构造方法创建线程后,线程进入新建状态; 就绪状态...

  • JVM常用命令之------jconsole与jvisualvm

    jconsole与jvisualvm jvisualvm同jconsole都是一个基于图形化界面的、可以查看本地及...

  • 4 多线程

    多线程 线程的状态 新状态 就绪状态 运行状态 阻塞状态 终止状态 线程的优先级 1--10, 默认为5,但线程优...

网友评论

    本文标题:结合jvisualvm一次性看清线程状态

    本文链接:https://www.haomeiwen.com/subject/meoisrtx.html