JVM · Java虚拟机原理 · JVM上语言·框架· 生态系统大数据Java 核心技术

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

2022-04-12  本文已影响0人  pq217

前言

本文主要结合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异常

总结

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

上一篇 下一篇

猜你喜欢

热点阅读