线程间的通信机制与生产者消费者案例

news2025/2/8 8:29:11

线程间的通信机制与生产者消费者案例

线程间通信

为什么要处理线程间通信:

当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些通信机制,可以协调他们的工作,以此实现多线程共同操作一份数据。

比如:线程A用来生产包子,线程B用来吃包子,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,此时B线程必须等A线程完成后才执行,那么线程A与线程B之间就需要线程通信,即——等待唤醒机制。

等待唤醒机制

这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。

在一个线程满足某个条件时,就进入等待状态(wait() / wait(time)),等待某个线程执行完他们的指定代码过后再将其唤醒(notify());或可以指定wait的时间,等时间到了自动唤醒;在有多个线程进行等待时,如果需要,可以使用notifyAll()来唤醒所有的等待线程。wait/notify就是线程间的一种协作机制。

  1. wait:线程不再活动,不再参与调度,进入wait set中,因此不会浪费CPU资源,也不会去竞争锁了,这时的线程状态是WAITING或TIMED-WAITING。他还要等着别的线程执行一个特别的动作,也即“通知(notify)”或者等待时间到,在这个对象上等待的线程从wait set中释放出来,重新进入到调度队列(ready queue)中。

  2. notify:则选取所通知对象的wait set中的一个线程释放;

  3. notifyAll:则释放所通知对象的wait set上的全部线程。

    1. 线程间通信的理解
    当我们`需要多个线程`来共同完成一件任务,并且我们希望他们`有规律的执行`,那么多线程之间需要一些通信机制,
    可以协调他们的工作,以此实现多线程共同操作一份数据。
    
    2. 涉及到三个方法的使用:
    wait(): 线程一旦执行此方法,就进入等待状态。同时,会释放对同步监视器的调用。
    notify(): 一旦执行此方法,就会唤醒被wait()的线程中优先级最高的那一个线程。(如果被wait的多个线程的优先级相同,
              则随机唤醒一个)。被唤醒的线程从当初被wait的位置继续执行。
    notifyAll(): 一旦执行此方法,就会唤醒所有被wait的线程。
    
    
    3.注意点:
    > 此三个方法的使用,必须在同步代码块或者同步方法中。
      (超纲:Lock需要配合Condition实现线程间的通信)
    > 此三个方法的调用者,必须是同步监视器。否则会报IllegalMonitorStateException异常。
    > 此三个方法声明在Object类中。(这样才能每个同步监视器都能调用的到,同步监视器又是任何唯一的对象)
    
    
    
    4. 案例:
    案例1:使用两个线程打印1-100.线程1,线程2 交替打印
    
    案例2:生产者&消费者
    生产者(Producer)将产品交给店员(Clerk),而消费者(Consumer)从店员处取走产品,店员一次只能持有
    固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放
    产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消
    费者来取走产品。
    
    
    5. wait()sleep()的区别?
    相同点:一旦执行,当前线程都会进入阻塞状态
    
    不同点:
    > 声明的位置:wait():声明在Object类中。
                  sleep():声明在Thread类中,静态的。
    > 使用的场景不同:wait():只能使用在同步代码块或同步方法中。
                      sleep():可以在任何需要使用的场景。
    > 使用在同步代码块或同步方法中:wait():一旦执行,会释放同步监视器。
                                    sleep():一旦执行,不会释放同步监视器。
    > 结束阻塞的方式:wait():到达结束时间,自动结束阻塞 或 通过被notify()唤醒,结束阻塞。
                      sleep():到达指定时间自动结束阻塞。
    

    案例1:

    package thread.demo05_communication;
    
    //案例1:使用两个线程打印1-100.线程1,线程2 交替打印
    
    public class PrintNumberTest {
        public static void main(String[] args) {
    
            PrintNumber p = new PrintNumber();
    
            Thread t1 = new Thread(p,"线程1");
            Thread t2 = new Thread(p,"线程2");
    
            t1.start();
            t2.start();
        }
    }
    
    
    class PrintNumber implements Runnable{
    
        private int number = 1;
        Object obj = new Object();
    
        @Override
        public void run() {
            while (true){
    
    //            synchronized (this) {
                synchronized (obj){
    
    //                this.notify();
                    obj.notify();//wait,notify,notifyAll这三个方法的调用必须是同步监视器。
    
                    if (number <= 100){
    
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        System.out.println(Thread.currentThread().getName() + ": " + number);
                        number++;
    
                        try {
    //                        this.wait();
                            obj.wait();//线程一旦执行此方法,就进入等待状态。同时,会释放对同步监视器的调用。
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                    }else {
                        break;
                    }
                }
            }
    
        }
    }
    

    输出:

    在这里插入图片描述

案例2:

package thread.demo05_communication;
/**
 *  案例2:生产者&消费者
 *  生产者(Producer)将产品交给店员(Clerk),而消费者(Consumer)从店员处取走产品,店员一次只能持有
 *  固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放
 *  产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消
 *  费者来取走产品。
 *
 *  分析:
 *  1. 是否是多线程问题?是,生产者,消费者
 *  2. 是否有共享数据?有! 共享数据是:产品
 *  3. 是否有线程安全问题? 有!因为有共享数据
 *  4. 是否需要处理线程安全问题?是! 如何处理?使用同步机制
 *  5. 是否存在线程间的通信? 存在。
 *
 */

public class ProducerConsumerTest {
    public static void main(String[] args) {

        Clerk clerk = new Clerk();

        Producer pro1 = new Producer(clerk);
        Consumer con1 = new Consumer(clerk);
        Consumer con2 = new Consumer(clerk);

        pro1.setName("生产者1");
        con1.setName("消费者1");
        con2.setName("消费者2");

        pro1.start();
        con1.start();
        con2.start();


    }
}

class Clerk{    //店员

    private int productNum = 0; //产品的数量

    //增加产品数量的方法
    public synchronized void addProduct(){

        if (productNum >= 20){
            //等待
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
        productNum++;
        System.out.println(Thread.currentThread().getName() + "生产了第" + productNum + "个产品");

        //唤醒
        notifyAll();
        }

    }

    //减少产品数量的方法
    public synchronized void minusProduct(){

        if (productNum <= 0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            System.out.println(Thread.currentThread().getName() + "消费了第" + productNum + "个产品");
            productNum--;

            //唤醒
            notifyAll();
        }

    }
}


class Producer extends Thread{ //生产者

    private Clerk clerk;

    public Producer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {

        while (true){
            System.out.println("生产者开始生产产品...");

            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.addProduct();

        }

    }
}

class Consumer extends Thread{ //消费者

    private Clerk clerk;

    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        while (true){

            System.out.println("消费者开始消费产品...");

            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            clerk.minusProduct();

        }
    }
}

输出:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/646385.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

中空百叶玻璃隔断怎么组装

以下是中空百叶玻璃隔断的组装步骤&#xff1a; 1. 准备材料&#xff1a;中空百叶玻璃、接头、U型槽、挂件、固定螺钉等。 2. 根据实际需要&#xff0c;将中空百叶玻璃按照尺寸进行切割。 3. 在地面上铺上一张软垫&#xff0c;将切好的玻璃放置在垫子上&#xff0c;然后在两侧标…

自动化部署工具 drone 部署文档 (使用于 vue java 部署 其他自行研究)

1.准备好docker环境 drone 官网 Drone CI – Automate Software Testing and Delivery 推荐使用宝塔傻瓜式一键安装宝塔面板 - 简单好用的Linux/Windows服务器运维管理面板 安装好docker环境之后开始部署容器 这里用到的git版本软件是gogs,其他自己研究 docker版的gogs和实…

Linux内存管理 —— 文件系统缓存和匿名页的交换

转自&#xff1a;https://www.cnblogs.com/wangshaowei/p/14089132.html 文件页 内存回收&#xff0c;也就是系统释放掉可以回收的内存&#xff0c;比如缓存和缓冲区&#xff0c;就属于可回收内存。它们在内存管理中&#xff0c;通常被叫做文件页&#xff08;File-backed Pag…

B045-jQuery案例实战BootStrap

目录 一、BootStrap概述二、BootStrap使用1.起步1.下载或放入BootStrap文件到本地工程里2.在html代码中引入1个css文件和两个js文件3.复制模板代码到本地html文件里会自动应用成bootStrap图样中的效果0.实战案例-环境准备0.实战案例-基本模板 2.布局容器3.栅格系统4.常用组件表…

echarts里type为custom,自定义配置renderItem为柱状形状

echarts里type为custom&#xff0c;自定义配置renderItem为柱状形状 echarts里type为custom&#xff0c;自定义配置renderItem为柱状形状 echarts里type为custom&#xff0c;自定义配置renderItem为柱状形状 主要功能&#xff1a;文本超过柱状形状就隐藏否则就显示&#xff0c;…

风口上的AIGC,技术人才动不动就年薪百万?

2023年&#xff0c;职场人都在讨论什么&#xff1f; 自今年3月以来&#xff0c;随着ChatGPT应用持续走俏&#xff0c;AIGC领域抢人大战盛况空前。随之而来的便是“AI取代人类”“10亿打工人被革命”&#xff0c;AI的发展速度和步伐&#xff0c;超乎我们预期&#xff0c;也影响…

MySQL - 第0节 - MySQL在Centos 7环境安装

1.安装前说明 • 安装与卸载过程中&#xff0c;用户全部切换成为root&#xff0c;一旦安装&#xff0c;普通用户也能够使用。 • 初期练习&#xff0c;mysql不进行用户管理&#xff0c;全部使用root进行&#xff0c;后面学了用户管理&#xff0c;再考虑新建普通用户。 注&#…

互联网医院资质申请条件|互联网医院申请流程

互联网医院是医疗行业应用互联网技术的一种创新形态&#xff0c;它为患者提供便捷的医疗服务&#xff0c;改善了医疗资源的不足和分散情况。在中国&#xff0c;互联网医院的发展也越来越受到重视&#xff0c;互联网医院牌照的申请流程和需要的资料也成为了关注的焦点。 一、互…

SpringCloud 中各微服务使用 springcloud-config 获取配置文件时,配置信息无法实时刷新

文章目录 1、引入actuator监控2、修改 yml3、添加注解 RefreshScope4、业务操作5、发送通知&#xff0c;即可生效6、思考 使用 springcloud-config 作为 服务配置 管理时&#xff0c; 当对 git 上的配置进行修改后&#xff0c; 各微服务客户端 配置无法 实时刷新&#xff0c;需…

【Flutter】Flutter 切换语言 当前页面不刷新

文章目录 一、前言二、Flutter 多语言支持的基础知识三、如何在 Flutter 中设置语言四、如何在 Flutter 中切换语言五、代码示例六、完整代码七、总结 一、前言 大家好&#xff0c;今天我们要探讨的主题是如何在 Flutter 中切换语言&#xff0c;而不需要刷新当前页面。这是一个…

vue-element-admin - 最新完美解决项目是英文的问题,将英文变成中文的汉化处理详细教程(克隆完项目后不是中文的解决方法)

前言 网上的教程(并且都是好几年前的了)克隆运行后界面文字全都是英文的,如果您想要中文汉语版本请使用本文的方案。 本文 解决了克隆运行 vue-element-admin 项目后,默认是英文的问题!将语言设置为中文的详细教程! 官方文档说的很晦涩,您可以按照本教程步骤进行操作即…

log4j配置说明

目录&#xff1a; 1、 引入jar包使用 2、 使用 3、 配置文件 4、 配置 5、 说明 说明&#xff1a;2015年9月&#xff0c;Apache软件基金业宣布&#xff0c;Log4j不在维护&#xff0c;建议所有相关项目升级到Log4j2。 1.引入jar包使用 <dependency><groupI…

vscode 调试

目录 准备 GDB 调试方法 问题 准备 然后点击 文件-打开文件夹&#xff0c;找到创建的代码路径&#xff0c;确定后&#xff0c;在左侧的资源管理器可以看到代码文件。 第一次运行需要安装 c 的扩展&#xff0c;在扩展页面中&#xff0c;安装 C/C 编译注意一定要加上 -g 指令…

SpringBoot项目配置方式及优先级

说明&#xff1a;SpringBoot支持以下五种方式配置方式&#xff0c;例如将项目的Tomcat端口从8080&#xff0c;更改为9000&#xff0c;可以使用如下方式配置 【方式一】命令行参数 在启动窗口&#xff0c;鼠标右键&#xff0c;选择“Edit Configurations”&#xff0c;在弹出来…

开会糟透了?试试无声会议吧

引言 无声会议(Silent Meeting) 是一种已被成功实践但却鲜有公开资料的开会方法&#xff0c;本文会探寻下它的有趣历史&#xff0c;并介绍如何通过它的小小改变让会议变得不再糟糕。 文档是无声会议的核心基础&#xff0c;本文会引出 Coda 和 Mojidoc(妙记多) 这两款让你的会…

泛微数字化国有资产管理平台,以智能增效能

2021年国务院发布的《行政事业性国有资产管理条例》中要求应当建立健全行政事业性国有资产管理机制&#xff0c;加强对本级行政事业性国有资产的管理。 随着数字化转型的深入&#xff0c;越来越多的行政事业单位、国有企业运用数字化的理念、思路、方法&#xff0c;以智能增效…

AI工具助力创意与写作:19岁小伙每月赚3万,学会这些你也能轻松做到!

导语&#xff1a;在数字时代&#xff0c;人工智能为我们提供了许多创新的工具和资源&#xff0c;助力我们在各个领域实现创意和写作的目标。本文将介绍五款热门的AI工具&#xff1a;Copyai、Flikiai、Notion、TomeAl和Writesonic&#xff0c;它们分别在写作、视频配音、文本编辑…

Java调用Midjourney进行AI画图原生版抓包实现支持中文

用途介绍 Midjourney是一个目前优秀的AI画图工具&#xff0c;不挂梯无法直接访问 本代码主要用于搭建镜像站使用 适合人群 本代码不适合新手&#xff0c;建议使用过okhttp、且具有二开能力的同学使用~ 实现原理 通过调用发送信息接口发送请求&#xff0c;通过轮询房间消息…

【2023最全教程】Web自动化测试怎么做?Web自动化测试的详细流程和步骤

一、什么是web自动化测试 自动化&#xff08;Automation&#xff09;是指机器设备、系统或过程&#xff08;生产、管理过程&#xff09;在没有人或较少人的直接参与下&#xff0c;按照人的要求&#xff0c;经过自动检测、信息处理、分析判断、操纵控制&#xff0c;实现预期的目…

Spring @Autowired注入太坤肋了 我们自己写一个

1、 背景 众所周知该注解是Spring中用于依赖注入的注解&#xff0c;但是该注解只是简单根据类型的注入&#xff0c; 并且如果该类型存在多个实现类的情况下无法抉择具体哪一个实现类就会抛出错误&#xff0c;除非搭配Qualifier注解然后使用硬编码的方式去指定Bean的名字去抉择…