什么是线程状态,线程的等待唤醒机制?

线程状态

线程状态概述

  1. 当创建线程启动后,并不会直接进入可执行状态,也不会一直处于执行状态。在线程的生命周期中共有6中线程状态(在API中 java.lang.Thread.State)
线程状态状态发生条件·
new(新建)线程刚被创建,但是还没有启动(没调start方法)
Runnable(就绪)线程可以在java虚拟机中运行的状态
Blocked(锁阻塞)当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入blocked状态,当该线程持有锁时,该线程将变成Runnable状态
Waiting(无限等待)一个线程在等待另一个线程执行一个唤醒动作时,该线程进入Waiting状态。进入这个状态后不能自动唤醒,必须等待另一个线程调用notifyAll和notify方法才能唤醒
Timed Waiting(计时等待)同Waiting状态,有几个方法有超时参数,调用他们将进入 Timed Waiting状态。这一状态将一直保持到超时期限满或者接受到唤醒通知,带有超时参数常用方法有Thread.sleep,object.wait
Teminated(被终止)因run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡

Timed Waiting(计时等待)状态
案例
实现一个计数器,计数到100,在每个数字之间暂停1秒,每隔10个数字输出一个字符串:
代码

public class Mythread  extends  Thread{

    public void run(){
        for (int i = 0; i < 100; i++) {
            //没隔10数字输出一个
            if ((i)%10==0) {
                System.out.println("----"+i);
            }
            System.out.print(i);
            try {
                //线程睡眠1秒
                Thread.sleep(1000);
                System.out.print("线程睡眠1秒!\n");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
    new Mythread().start();
    }
}

sleep方法使用非常就简单,要记住一下几点:

  1. 进入 TIMED_WAITING 状态的一种常见情形是调用的 sleep 方法,单独的线程也可以调用,不一定非要有协 作关系。
  2. 为了让其他线程有机会执行,可以将Thread.sleep()的调用放线程run()之内。这样才能保证该线程执行过程 中会睡眠
  3. sleep与锁无关,线程睡眠到期自动苏醒,并返回到Runnable(可运行)状态。
    在这里插入图片描述
    Blocked(锁阻塞) 状态
    介绍:
    线程A与线程B代码中使用同一锁,如果线程A获 取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。
    在这里插入图片描述
    Waiting(无限等待)状态
    代码举例:
public class WaitingTest {
    public static Object object = new Object();

    public static void main(String[] args) {
        //演示waiting
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (object) {

                        try {
                            //获取线程对象名称
                            System.out.println(Thread.currentThread().getName() +
                                    "=== 获取 到锁对象,调用wait方法,进入waiting状态,释放锁对象");
                            object.wait();//没有参数就是无限等待
                            //obj.wait(5000); //计时等待, 5秒 时间到,自动醒来
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() +
                                "=== 从 waiting状态醒来,获取到锁对象,继续执行了");
                    }
                }
            }
        },"等待线程").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
               // while (true){ //每隔3秒 唤醒一次

                try {
                    System.out.println( Thread.currentThread().getName() +"----- 等待3秒 钟");
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (object){
                    System.out.println( Thread.currentThread().getName()

                            +"----- 获取到 锁对象,调用notify方法,释放锁对象");
                    object.notify();
                }
                //}
            }
        },"唤醒线程").start();
    }
}

结果:

等待线程=== 获取 到锁对象,调用wait方法,进入waiting状态,释放锁对象
唤醒线程----- 等待3秒 钟
唤醒线程----- 获取到 锁对象,调用notify方法,释放锁对象
等待线程=== 从 waiting状态醒来,获取到锁对象,继续执行了
等待线程=== 获取 到锁对象,调用wait方法,进入waiting状态,释放锁对象

通过上述案例我们会发现,一个调用了某个对象的 Object.wait 方法的线程会等待另一个线程调用此对象的Object.notify()方法 或 Object.notifyAll()方法。 其实waiting状态并不是一个线程的操作,它体现的是多个线程间的通信,可以理解为多个线程之间的协作关系, 多个线程会争取锁,同时相互之间又存在协作关系。就好比在公司里你和你的同事们,你们可能存在晋升时的竞 争,但更多时候你们更多是一起合作以完成某些任务。 当多个线程协作时,比如A,B线程,如果A线程在Runnable(可运行)状态中调用了wait()方法那么A线程就进入 了Waiting(无限等待)状态,同时失去了同步锁。假如这个时候B线程获取到了同步锁,在运行状态中调用了 notify()方法,那么就会将无限等待的A线程唤醒。注意是唤醒,如果获取到锁对象,那么A线程唤醒后就进入 Runnable(可运行)状态;如果没有获取锁对象,那么就进入到Blocked(锁阻塞状态)。

在这里插入图片描述
完整流程
在这里插入图片描述
我们在翻阅API的时候会发现Timed Waiting(计时等待) 与 Waiting(无限等待) 状态联系还是很紧密的, 比如Waiting(无限等待) 状态中wait方法是空参的,而timed waiting(计时等待) 中wait方法是带参的。 这种带参的方法,其实是一种倒计时操作,相当于我们生活中的小闹钟,我们设定好时间,到时通知,可是 如
果提前得到(唤醒)通知,那么设定好时间在通知也就显得多此一举了,那么这种设计方案其实是一举两 得。如果没有得到(唤醒)通知,那么线程就处于Timed Waiting状态,直到倒计时完毕自动醒来;如果在倒 计时期间得到(唤醒)通知,那么线程从Timed Waiting状态立刻唤醒。

等待唤醒机制

线程之间通讯
概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
比如:线程A用来生成肉的,线程B用来吃肉的,肉可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题。
在这里插入图片描述
为什么要处理线程间通信:
多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
如何保证线程间通信有效利用资源:
多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。
什么是等待唤醒机制?

这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。
就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。
wait/notify 就是线程间的一种协作机制。

等待唤醒中的方法

  1. wait:线程不再活动,不再参与调度,进入 wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中
  2. notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
  3. notifyAll:则释放所通知对象的 wait set 上的全部线程。

注意:
哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用wait 方法之后的地方恢复执行。
总结:
如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态

调用wait和notify方法需要注意的细节

  1. wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
  2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

生产者与消费者
等待唤醒机制其实就是经典的“生产者与消费者
就拿生产肉消费肉来说等待唤醒机制如何有效利用资源:

猪肉铺线程生产肉,吃货线程消费肉。当肉没有时(肉状态为false),吃货线程等待,肉铺线程生产肉(即 肉状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有肉了,那么肉铺线程进入等待状态。接下 来,吃货线程能否进一步执行则取决于锁的获取情况。如果吃货获取到锁,那么就执行吃肉动作,肉吃完(肉状态为 false),并通知肉铺线程(解除肉铺的等待状态),吃货线程进入等待。肉铺线程能否进一步执行则取决于锁的获取 情况。

代码
肉资源类

public class Rou {
    String zhurou;
    String niurou;

    //肉资源 是否存在 肉资源状态
    boolean flag=false;
}

吃货类

public class Chihuo extends Thread {
    private Rou rou;

    public Chihuo(String name, Rou rou) {
        super(name);
        this.rou = rou;
    }
    @Override
    public  void run(){
        while (true){
            synchronized (rou){
                //没肉
                if (rou.flag==false){
                    try {

                        rou.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("吃货再吃"+rou.niurou+rou.zhurou);
                rou.flag=false;
                rou.notify();
            }
        }
    }
}

肉铺

public class RouPu extends  Thread{
    private Rou rou;

    public RouPu(String name, Rou rou) {
        super(name);
        this.rou = rou;
    }
    public  void run(){
        int count=0;
        //杀猪取肉
        while (true){
            //同步
            synchronized (rou){
                //有肉
                if (rou.flag==true){
                    try {
                        rou.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //没肉,杀猪取肉
                System.out.println("肉铺开始杀猪");
                if (count%2==0){
                    //猪肉 牛肉
                    rou.zhurou="牛肉";
                    rou.niurou="爆炒";

                }else {
                    rou.niurou="炸";
                    rou.zhurou="耗子肉";
                }
                count++;
                rou.flag=true;
                System.out.println("肉造好了:"+rou.niurou+rou.zhurou);
                System.out.println("吃货来吃吧");

                //唤醒等待线程 (吃货)
                rou.notify();

            }
        }
    }
}

测试类

public class test {
    public static void main(String[] args) {
        Rou rou = new Rou();
        Chihuo chihuo = new Chihuo("吃货", rou);
        RouPu rouPu = new RouPu("肉铺", rou);

        chihuo.start();
        rouPu.start();

    }
}

输出

:炸耗子肉
吃货来吃吧
吃货再吃炸耗子肉
肉铺开始杀猪
肉造好了:爆炒牛肉
吃货来吃吧
吃货再吃爆炒牛肉
肉铺开始杀猪
肉造好了:炸耗子肉
吃货来吃吧
吃货再吃炸耗子肉


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值