Java 线程间的通信机制(等待和唤醒机制)

一、 线程间通信

概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
例如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题。

1.1线程之间为什么需要进行通信?

       我们都知道,要想能够去执行一个线程,首先这个线程需要获取CPU的执行权,当这个线程执行完毕之后,就会释放CPU资源,并发执行的时候,剩下的处于就绪状态的线程就会一起去争夺CPU的执行权,谁抢到谁就执行。但是在开发过程中,我们可能需要多个线程进行协调配合来完成一件事。并且需要线程之间有规律的去执行任务。这就需要线程之间相互通信了。也就是说当多个线程共同去争夺同一个cpu资源的时候,只能有一个线程能够抢到,其他没抢到的就会进入阻塞状态,等待抢到cpu资源的线程执行结束再去抢夺。例如:烤鸭店老板A将烤鸭生产好之后,就叫顾客B过来吃。B吃完之后,通知A继续生产制作。

注意:

A先生产,此时B在等待A生产好。
A生产好之后,通知B来吃,相当于B被A唤醒。

线程之间的通信依靠的是wait()notify()、notifyAll()方法进行协调。这三个方法都是定义在了Object类中。

1.2 如何保证线程间通信有效利用资源:

       多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。

1.3 什么是等待唤醒机制

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

二、为什么要定义在Object类中?

       一提到定义在Object中就会想到同步锁,之前的一篇文章:线程安全—实现安全卖票中,就介绍了Java中每个对象有且仅有一个对象锁。也就是说所有的java对象都可以是同步对象,既然所有对象都能是同步对象,而且有wait()、notify()、notifyAll()方法,当然应该将这三个方法定义到Object超类中。

2.1 等待与唤醒方法基本概念:

wait():
  当线程A调用wait()方法后,释放同步锁,进入阻塞状态,然后加入到等待锁对象的队列中。



notify():
  线程B获取到同步锁之后,调用notify()方法,从等待锁的队列中唤醒一个线程(被唤醒的线程状态由等待转变成就绪,等线程B执行完毕释放了锁资源之后,被唤醒的线程获取到锁之后就会去执行该线程)



notifyAll():
  线程B获取到同步锁之后,调用notifyAll()方法,会唤醒等待锁队列中所有的线程,等待线程B执行完之后释放锁资源,被唤醒的线程去争夺锁资源,获取到锁对象的线程会去执行相应的逻辑。


2.2 notify()和notifyAll()的区别总结:

   notify()方法 仅仅会去通知等待队列中的其中一个线程,并且我们并不知道哪个线程会被唤醒,但是notifyAll()方法会唤醒等待队列中的所有处于等待状态的线程(如果此时我们的等待队列中只有一个处于等待状态的线程,那么两种唤醒方法的效果一样,但是如果等待队列中有两个或两个以上的等待状态线程,那么就需要主要两种唤醒方法的区别了)

三、sleep()方法和wait()方法有何区别?

可以结合:线程的生命周期来看。

sleep()他是定义在Thread类中的一个方法,当调用此方法的时候,线程可以由RUNNING状态转换为TIMED_WAITING状态,线程执行此方法的时候,将会释放CPU执行权,但是该线程依然会持有当前拥有的锁对象,(它释放了CPU的执行权之后,其他线程可以使用此CPU执行权去执行不依赖此对象锁的任务)。此方法用在什么位置没有特殊要求。

wait()他是定义在Object类中的一个方法,当调用此方法的时候,线程可以由RUNNING状态转换为WAITING状态,此状态也可以理解为“无线等待”状态,他需要配合notify()和notifyAll()方法来唤醒线程。另外,线程执行此方法的时候,将会释放CPU的执行权和持有的锁。此方法必须要用在synchronized同步代码块中。

 注意:以上方法需要用在同步代码块/方法中。调用wait()方法和notify()/notifyAll()方法的锁对象必须是同一个。

例题:

情景:包子铺生产者消费者案例:(包子铺老板A将包子生产好之后,叫吃货B过来吃,B吃完之后,通知A继续生产制作。)

分析:

代码:

实体类

public class BaoZi {
    String name;//包子的名称

    Boolean flag;//包子的状态(true表示存在  false 表示不存在)
}

 消费者类(吃货B)

public class ChiHuo extends Thread{
    BaoZi baoZi;

    //构造函数:用来指定线程的名称和操作资源
    public ChiHuo(String name,BaoZi baoZi){
        super(name);
        this.baoZi=baoZi;
    }
    public void run(){
        String threadName=Thread.currentThread().getName();
        int count=0;
        while(true){
            synchronized (baoZi){
                count++;
                if (count>10){
                    break;
                }
                if(baoZi.flag){//如果包子存在
                    System.out.println(threadName+"开始吃"+baoZi.name);
                    baoZi.flag=false;//修改状态
                    baoZi.notify();//唤醒其他资源状态
                }else{
                    //如果包子不存在
                    try {
                        baoZi.wait();//进入等待状态
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

 生产者类(包子铺老板A)

public class ZaoCanDian extends Thread{
    BaoZi baoZi;

    //构造函数:用来指定线程的名称和操作资源
    public ZaoCanDian(String name, BaoZi baoZi) {
        super(name);
        this.baoZi = baoZi;
    }

    public void run() {
        String threadName = Thread.currentThread().getName();
        int count = 0;
        while (true) {
            synchronized (baoZi) {
                count++;
                if (count > 10) {
                    break;
                }
                if (baoZi.flag) {//如果包子存在
                    try {
                        baoZi.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    //如果包子不存在
                    System.out.println(threadName + "开始制作" + baoZi.name);
                    baoZi.flag=true;//更改包子状态
                    baoZi.notify();//唤醒同一资源下的其他线程
                }
            }
        }
    }
}

 测试类

public class ThreadTest {
    public static void main(String[] args) {
        //定义资源对象
        BaoZi baoZi=new BaoZi();
        baoZi.name="豆沙包";
        baoZi.flag=true;

        //定义两个线程,起名字且操作同一对象
        ChiHuo ch=new ChiHuo("吃货",baoZi);
        ZaoCanDian zcd=new ZaoCanDian("圆滚滚包子铺",baoZi);

        //启动线程
        zcd.start();
        ch.start();
    }
}

 测试结果

吃货开始吃豆沙包
圆滚滚包子铺开始制作豆沙包
吃货开始吃豆沙包
圆滚滚包子铺开始制作豆沙包
吃货开始吃豆沙包
圆滚滚包子铺开始制作豆沙包
吃货开始吃豆沙包
圆滚滚包子铺开始制作豆沙包
吃货开始吃豆沙包
圆滚滚包子铺开始制作豆沙包
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值