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

前言

本文主要结合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的等待过程不响应
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,539评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,594评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,871评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,963评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,984评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,763评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,468评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,850评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,002评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,144评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,823评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,483评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,026评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,150评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,415评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,092评论 2 355

推荐阅读更多精彩内容