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

追求适度,才能走向成功;人在顶峰,迈步就是下坡;身在低谷,抬足既是登高;弦,绷得太紧会断;人,思虑过度会疯;水至清无鱼,人至真无友,山至高无树;适度,不是中庸,而是一种明智的生活态度。

导读:本篇文章讲解 Java 线程间的通信机制(等待和唤醒机制),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

一、 线程间通信

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

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

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

注意:

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

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

线程之间的通信依靠的是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继续生产制作。)

分析:

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

代码:

实体类

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();
    }
}

 测试结果

吃货开始吃豆沙包
圆滚滚包子铺开始制作豆沙包
吃货开始吃豆沙包
圆滚滚包子铺开始制作豆沙包
吃货开始吃豆沙包
圆滚滚包子铺开始制作豆沙包
吃货开始吃豆沙包
圆滚滚包子铺开始制作豆沙包
吃货开始吃豆沙包
圆滚滚包子铺开始制作豆沙包

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/130151.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!