线程状态聊聊

本文详细解析了线程的六种状态:新建、可运行、终结、阻塞、等待和有时限等待,并通过实例演示了synchronized、wait、notify和notifyAll在并发控制中的作用。重点讲解了等待集合和阻塞集合的区别,以及notify操作唤醒线程的顺序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程状态

线程状态,这个问题好像很容易,但也容易模糊,而且有好几个版本,我就以我自认为对的版本,记录下。

线程6状态

在这里插入图片描述

  1. 新建
    当一个线程对象被创建,但还未调用 start 方法时处于新建状态
    此时未与操作系统底层线程关联
  2. 可运行
    调用了 start 方法,就会由新建进入可运行
    此时与底层线程关联,由操作系统调度执行
  3. 终结
    线程内代码已经执行完毕,由可运行进入终结
    此时会取消与底层线程关联
  4. 阻塞
    当获取锁失败后,由可运行进入 Monitor 的阻塞队列阻塞,此时不占用 cpu 时间
    当持锁线程释放锁时,会按照一定规则唤醒阻塞队列中的阻塞线程,唤醒后的线程进入可运行状态
  5. 等待
    当获取锁成功后,但由于条件不满足,调用了 wait() 方法,此时从可运行状态释放锁进入 Monitor 等待集合等待,同样不占用 cpu 时间
    当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的等待线程,恢复为可运行状态
  6. 有时限等待
    当获取锁成功后,但由于条件不满足,调用了 wait(long) 方法,此时从可运行状态释放锁进入 Monitor 等待集合进行有时限等待,同样不占用 cpu 时间
    当其它持锁线程调用 notify() 或 notifyAll() 方法,会按照一定规则唤醒等待集合中的有时限等待线程,恢复为可运行状态,并重新去竞争锁
    如果等待超时,也会从有时限等待状态恢复为可运行状态,并重新去竞争锁
    还有一种情况是调用 sleep(long) 方法也会从可运行状态进入有时限等待状态,但与 Monitor 无关,不需要主动唤醒,超时时间到自然恢复为可运行状态

线程疑问

  • 什么是上述的等待集合
  • 什么是阻塞集合
  • notity和notifyall唤醒的线程,有顺序吗

等待集合,阻塞集合

对于同一个对象上锁,只有先拿到这个锁的线程先进入到代码中,其他的线程,再在这个对象的阻塞队列中等待,这就是一种阻塞状态;
针对线程中执行wait方法,就会将线程放入到等待队列中,一旦调用notifyall方法,也就会将所有等待队列中对象,放入到阻塞队列中进行排队等待。
在这里插入图片描述

在这里插入图片描述
举例代码测试
1、测试第一个是否有对于同一把锁是否有执行顺序

public class TestSynchronized {
    static List<Thread> list = new ArrayList<>();
    static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(() -> {
                synchronized (lock) {
                    System.out.println(String.format("{%s}-thread executed",Thread.currentThread().getName()));
                    try {
                        //这里的睡眠没有什么意义,仅仅为了控制台打印的时候有个间隔 视觉效果好
                        TimeUnit.MILLISECONDS.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "t" + i);//给每个线程去了一个名字 t1 t2 t3 ....

            list.add(t);
        }

        System.out.println("---启动顺序 调度顺序或者说获取锁的顺序讲道理是正序0--9----");

        synchronized (lock) {
            for (Thread thread : list) {
                //这个打印主要是为了看到线程启动的顺序
                System.out.println(String.format("{%s}-启动顺序--正序0-9", thread.getName()));
                thread.start();//  CPU 调度?

                //间隔时间表示启动有先后顺序,保证加入到等待队列中
                //虽然我们的启动顺序是正序的(t1--t2),但是调度顺序是错乱的  t2---t1
                TimeUnit.MILLISECONDS.sleep(1);
            }
            System.out.println("-------执行顺序--正序9-0");
        }
    }
}

结果:放入线程是t0-t9,但是执行时是t9-t0,结合我上面画的图理解

2、测试第二个阻塞队列notifyAll之后的执行顺序

public class TestWaitOrder {

    static  Object object = new Object();//锁对象

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {//jack
            synchronized (object){
                System.out.println(String.format("{%s}-没有香烟 我去等待老板安排 先休息,安排好之后叫醒我",Thread.currentThread().getName()));
                try {
                    //jack线程进入阻塞,但是释放了锁
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(String.format("{%s}-开始工作,大中华真不错",Thread.currentThread().getName()));
            }
        }, "jack").start();
        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> {//jack
            synchronized (object){
                System.out.println(String.format("{%s}-没有香烟 我去等待老板安排 先休息,安排好之后叫醒我",Thread.currentThread().getName()));
                try {
                    //jack线程进入阻塞,但是释放了锁
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(String.format("{%s}-开始工作,大中华真不错",Thread.currentThread().getName()));
            }
        }, "tom").start();
        TimeUnit.SECONDS.sleep(1);

        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            synchronized (object){
                //log.debug("没有加钱,先休息不干活");
                System.out.println(String.format("{%s}-没有加钱,先休息不干活",Thread.currentThread().getName()));
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(String.format("{%s}-干活了----有钱能使鬼推",Thread.currentThread().getName()));
            }
        }, "rose").start();

        //5s之后叫醒jack
        TimeUnit.SECONDS.sleep(5);
        new Thread(() -> {//jack
            synchronized (object){
                System.out.println("买了两包大中华,给加钱了");
                object.notifyAll();
            }
        }, "boss").start();

    }
}

结果发现:放入顺序是jeck-tom-rose,叫醒之后的顺序是rose-tom-jeck,结合画的图理解

3、notify到底叫醒的是哪个线程,有规律吗?

将上述代码进行改造,只需将notifyAll变成notify,多测试几次,并且把上述线程(jack,tom,rose)顺序进行调整。

结果发现:调用notity之后,叫醒的第一个线程是最先放入等待队列中的(最早执行wait),这和notifyAll矛盾吗?其实不矛盾,可以理解为notify只是将第一个移到阻塞队列中了,nitifyAll是把几个都放到了阻塞队列中了,因为阻塞队列执行是从尾部弹出的,所以就能理解了。

4、等待队列中notify的线程会比阻塞队列的先执行吗?

判断等待队列中唤醒的是否插入到了阻塞队列的尾部,进行代码测试

public class TestWaitAndBlockOrder {

    static  Object object = new Object();//锁对象

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {//jack
            synchronized (object){
                System.out.println(String.format("{%s}-没有中华 我去等待老板安排 先休息,安排好之后叫醒我",Thread.currentThread().getName()));
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(String.format("{%s}-开始工作",Thread.currentThread().getName()));
            }
        }, "jack").start();


        TimeUnit.SECONDS.sleep(1);

        System.out.println("1s之后主线程获取锁");
        System.out.println("因为jack wait释放了锁,主线程能够获取到");
        System.out.println("-----------关键看打印顺序----------------");
        synchronized (object) {

            for (int i = 0; i < 5; i++) {
                new Thread(() -> {
                    synchronized (object) {
                        System.out.println(String.format("{%S}-那些默默无闻的普通人",Thread.currentThread().getName()));
                    }
                }, "t" + i).start();
                TimeUnit.MILLISECONDS.sleep(1);
            }

			//先唤醒jeck
            object.notifyAll();
        }

    }
}

这不能说明等待的线程加入到阻塞队列的尾部,也可能是巧合,那就调换下object.notifyAll()的位置,把这个代码移到普通线程前面

public class TestWaitAndBlockOrder {

    static  Object object = new Object();//锁对象

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {//jack
            synchronized (object){
                System.out.println(String.format("{%s}-没有中华 我去等待老板安排 先休息,安排好之后叫醒我",Thread.currentThread().getName()));
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(String.format("{%s}-开始工作",Thread.currentThread().getName()));
            }
        }, "jack").start();


        TimeUnit.SECONDS.sleep(1);

        System.out.println("1s之后主线程获取锁");
        System.out.println("因为jack wait释放了锁,主线程能够获取到");
        System.out.println("-----------关键看打印顺序----------------");
        synchronized (object) {
            //先唤醒jeck
            object.notifyAll();
            for (int i = 0; i < 5; i++) {
                new Thread(() -> {
                    synchronized (object) {
                        System.out.println(String.format("{%S}-那些默默无闻的普通人",Thread.currentThread().getName()));
                    }
                }, "t" + i).start();
                TimeUnit.MILLISECONDS.sleep(1);
            }

            //后唤醒杰克
           // object.notifyAll();
        }

    }
}

结果发现:等待队列中的线程唤醒后加入阻塞队列的尾部。

总结

block状态的线程,是有个阻塞的队列中存放的,而且是尾部先执行的意思;
wait状态的线程,有个等待队列中存放的,唤醒之后,加入阻塞队列的尾部;
wait状态先变成block状态,再进行执行,我是这么理解的,先姑且这么理解

学习大佬博客:并发编程系列——wait原理的讨论(1)

我只是知识进行理解记忆,学习请参考大佬博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值