美文网首页
线程的生命周期

线程的生命周期

作者: 谢宇鹏 | 来源:发表于2018-04-24 16:43 被阅读0次
    状态切换
    线程生命周期.png

    数字并不表示先后顺序

    阻塞

    三种,同步阻塞,等待阻塞,其他阻塞。
    同步阻塞(Entry Set),等待获取对象锁的同步队列,有些地方也叫锁池(每个对象都有一个内部锁,monitor监视器)。当某个线程访问一个对象中的Synchronize同步代码块时,如果发现该对象的监视器被别的线程持有,就进入该对象的 Entry Set,当前线程状态变成 BLOCKED
    等待阻塞(Wait Set),占有某个对象监视器的线程,调用该对象的 wait() 方法放弃监视器,就进入了该对象的 Wait Set,当前线程状态变成 WAITING(on object monitor)
    其他阻塞,调用sleep等方法进入阻塞状态,让出CPU,但不会放弃锁,当前线程进入 TIME_WAITING(sleeping) 状态。

    通过 jstack 命令查看线程状态变化
    package com.xieyupeng.springboot.Multithreading;
    
    public class ObjectWaitTest {
    
        synchronized void objectWait() throws InterruptedException{
            System.out.println(Thread.currentThread().getName() + "进来先休息一下");
            Thread.sleep(10000);
            System.out.println(Thread.currentThread().getName() + "休息完了放弃锁");
            this.wait();
            System.out.println(Thread.currentThread().getName() + "获得锁");
            Thread.sleep(5000);
        }
    
        synchronized void objectNotify() throws InterruptedException {
            this.notifyAll();
            System.out.println(Thread.currentThread().getName()
                           +"唤醒所有线程进入锁池并选择一个获取锁");
            Thread.sleep(10000);
         }
    
        public static void main(String[] args) throws InterruptedException {
            ObjectWaitTest test = new ObjectWaitTest();
            for (int i = 0 ; i < 3 ; i ++){
                new Thread(()->
                {
                    try {
                        System.out.println(Thread.currentThread().getName()+"调用同步块");
                        test.objectWait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }).start();
            }
            Thread.sleep(40000); //保证3个线程代码都执行了 wait方法
            test.objectNotify();
        }
    
    }
    
    

    执行上面的 main 函数,依次记录状态如下:

    • 0,1,2 三个线程依次调用同步块,0线程最先获取 test 对象的锁,1、2线程进入同步阻塞。
    1.png

    此时执行 jstack 命令,可以看到,0线程先占有了 test 对象的监视器,锁住了<0x00000000d675a770>地址,进入了代码块,执行过程中,调用了 sleep() 方法,状态变成了 TIMED_WAITING。同一时间,1、2线程由于获取不到 test 对象的监视器,进入了 test 对象的 Entry Set(waiting for monitor entry),一直等待给<0x00000000d675a770>地址上锁,状态变成 BLOCKED。3个线程都是对同一个地址的竞争,从这个地址后面括号里的描述也可以看出,就是和 test 对象相关的。如果这里使用 Lock 上锁的话,线程状态是 WAITING(parking)。

    A.png
    • 0线程 sleep 完了,调用了 wait() 方法放弃了 test 对象的监视器,进入等待阻塞;接着,2线程获得了 test 对象的监视器。 上一步中,2线程是比1线程 后进入阻塞队列的,也就是说,谁能在下一次获取对象的锁,不是由进入 Entry Set 的顺序决定的,还和其他因素有关,可以暂时理解为随机。对应线程图中的6。
    2.png

    通过 jstack 命令查看,in Object.wait(),0线程 调用了 test.wait(),线程状态切换成 WAITING,而2线程的状态 和 上一步的 0线程的一样,1线程依旧在Entry Set中。

    B.png
    • 3个线程都执行了 wait() 方法,都进入了 Wait Set,线程状态WAITING(on object monitor)。如果这里调用的是 Condition 的 wait() 方法的话,线程状态就是 WAITING(parking)
    3.png

    从线程 dump 文件中可以看出,全部变成了 WAITING 状态。图中箭头方向是程序运行的时间方向。比如2线程,首先执行了 run 方法,然后获得了对象锁,进入了同步方法 objectWait,接着调用 wait() 方法,进入等待阻塞队列。

    C.png
    • 主线程调用 test.notifyAll() ,唤醒所有调用过 test.wait() 进入等待阻塞的线程。执行对象的 nofity()/notifAll() 方法,必须要等到 synchronize 同步代码执行完毕才能获得锁。唤醒方法中特意加了一个 Thread.sleep(10000),就是为了测试这个。4图可以看出执行了 notifyAll() 方法,对应的 D图中,线程都变成了BLOCKED,说明它们被唤醒去竞争 test 对象锁了,但是锁仍然被主线程占据了,因为它还在 sleep,没有退出同步代码,所以线程们都被阻塞了。
    4.png D.png
    • 线程被唤醒后,只能有一个线程获取对象锁,所以又要竞争了,竞争成功变成就绪态,失败的仍然留在Wait Set,状态变成 BLOCKED。
    5.png

    从线程dump文件中可以看出,1线程获得了锁,但是执行了 sleep() 变成了 TIME_WAITING状态,说明是一个有时间期限的等待,waiting on condition 等待一个条件让它退出阻塞状态,这个条件就是时间到期。0和2线程是阻塞状态,但是从 in Object wait() 可以看出,它们还在 Wait Set 中。

    E.png
    • 程序执行完毕


      6.png

    参考:

    https://www.zhihu.com/question/64725629/answer/224047354
    https://www.cnblogs.com/zhengyun_ustc/archive/2013/01/06/dumpanalysis.html

    相关文章

      网友评论

          本文标题:线程的生命周期

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