【多线程(三)】生产者和消费者模式

news/2024/5/6 8:15:19/文章来源:https://blog.csdn.net/hihielite/article/details/128182525

文章目录

  • 3.生产者和消费者模式
    • 前言
    • 3.1生产者和消费者模式概述
    • 3.2生产者和消费者案例
    • 3.3 阻塞队列基本使用
    • 3.4 阻塞队列实现等待唤醒机制
    • 总结

3.生产者和消费者模式

前言

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线
程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必
须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大
于生产者,那么消费者就必须等待生产者。为了解决这种生产消费能力不均衡的问
题,所以便有了生产者和消费者模式。

3.1生产者和消费者模式概述

  • 概述

    • 生产者消费者模式是一个十分经典的 多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。
    • 所谓生产者消费者问题,实际上主要包含了两类线程:
      • 一类是生产者线程用于生产数据
      • 一类是消费者线程用于消费数据
    • 为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库
    • 生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为。
    • 消费者只需要从共享数据区去获取数据,并不需要关心生产者的行为。
  • Object 类的等待唤醒方法

方法名说明
void wait()导致当前线程等待,直到另一个线程调用该对象的 notify()方法或notifyAll()方法
void notify()唤醒正在等待对象监视器的单个线程
void notifyAll()唤醒正在等待对象监视器的所有线程

3.2生产者和消费者案例

  • 案例需求

    • 桌子类(Desk): 定义表示包子数量的变量,定义锁对象变量,定义标记桌子上有无包子的变量。
    • 生产者(Cooker): 继承 Thread 类,重写 run() 方法,设置线程任务。
      • 1.判断是否有包子,决定当前线程是否执行
      • 2.如果有包子,就进入等待状态,如果没有包子,继续执行,生产包子
      • 3.生产包子之后,更新桌子上包子的状态,唤醒消费者消费包子
    • 消费者类(Foodie): 继承 Thread类,重写 run() 方法,设置线程任务。
      • 1.判断是否有包子,决定当前线程是否执行
      • 2.如果没有包子 ,就进入等待状态,如果有包子,就消费包子
      • 3.消费包子后,更新桌子上包子状态,唤醒生产者生产包子
    • 测试类(Demo): 里面有 main() 方法,main() 方法中的代码步骤如下
      • 1.创建生产者线程和消费者线程对象
      • 2.分别开启两个线程
  • Cooker(生产者)类:

public class Cooker extends Thread {private Desk desk;public Cooker(Desk desk) {this.desk=desk;}/*生产者步骤:1.判断桌子上是否有汉堡包如果有就等待,如果没有就生产,2.把汉堡包放在桌子上。3.叫醒等待的消费者开吃*/@Overridepublic void run() {while(true){synchronized (desk.getLock()){if(desk.getCount()==0){break;}else{if(!desk.isFlag()){//生产System.out.println("厨师正在生产汉堡包");desk.setFlag(true);desk.getLock().notifyAll();}else{try {desk.getLock().wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}
}
  • Foodie(消费者)类:
public class Foodie extends Thread{private Desk desk;public Foodie(Desk desk) {this.desk=desk;}//套路://1.while(true)死循环//2.synchronized 锁,锁对象要唯一//3.判断,共享数据是否结束,结束//4.判断,共享数据是否结束,没有结束@Overridepublic void run() {
/*消费者步骤:1.判断桌子上是否有汉堡包2.如果没有就等待3.如果有就开吃4.吃完之后,桌子上的汉堡包就没有了叫醒等待的生产者继续生产汉堡包的总数量减一*/while(true){synchronized (desk.getLock()){if(desk.getCount() == 0){break;}else{if(desk.isFlag()){//有System.out.println("吃货在吃汉堡包");desk.setFlag(false);desk.getLock().notifyAll();desk.setCount(desk.getCount()-1);}else{//没有就等待//使用什么对象当作锁,那么就必须用这个对象去调用等待和唤醒的方法try {desk.getLock().wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}
}
  • Desk(共享数据区)类:
public class Desk {//定义一个标记//true 就表示桌子上有汉堡包的,此时允许吃货执行。//false 就表示桌子上没有汉堡包的,此时允许厨师执行。// public static boolean flag = false;private  boolean flag;//汉堡包的总数量
//    public static int count = 10;//以后我们在使用这种必须有默认值的变量private int count ;//锁对象
//    public static final Object lock = new Object();private final Object lock = new Object();public Desk() {this(false,10);}public Desk(boolean flag, int count) {this.flag = flag;this.count = count;}public boolean isFlag() {return flag;}public void setFlag(boolean flag) {this.flag = flag;}public int getCount() {return count;}public void setCount(int count) {this.count = count;}public Object getLock() {return lock;}@Overridepublic String toString() {return "Desk{" +"flag=" + flag +", count=" + count +", lock=" + lock +'}';}
}
  • Demo(测试)类:
public class Demo {public static void main(String[] args) {/*消费者步骤:1.判断桌子上是否有汉堡包2.如果没有就等待3.如果有就开吃4.吃完之后,桌子上的汉堡包就没有了叫醒等待的生产者继续生产汉堡包的总数量减一*//*生产者步骤:1.判断桌子上是否有汉堡包如果有就等待,如果没有就生产,2.把汉堡包放在桌子上。3.叫醒等待的消费者开吃*/Desk desk = new Desk();Foodie f = new Foodie(desk);Cooker c = new Cooker(desk);f.start();c.start();}
}
  • 运行结果:
    在这里插入图片描述

3.3 阻塞队列基本使用

  • 阻塞队列继承结构
    在这里插入图片描述

  • 常见 BlockingQueue:

    • ArrayBlockingQueue:底层是数组,有界
    • LinkedBlockingQueue:底层是链表,无界。但不是真正的无界,最大为 int 的最大值
  • BlockingQueue的核心方法:

    • put(anObject): 将参数放入队列,如果放不进去会阻塞。
    • take(): 取出第一个数据,取不到会阻塞。
  • 代码示例

public class Demo {public static void main(String[] args) throws InterruptedException {//阻塞队列的基本用法//创建阻塞队列的对象,并规定里边的容量为1ArrayBlockingQueue<String> arrayBlockingQueue = new ArrayBlockingQueue<>(1);//存储元素arrayBlockingQueue.put("汉堡包");//取元素System.out.println(arrayBlockingQueue.take());System.out.println(arrayBlockingQueue.take());System.out.println("程序结束了...");}
}
  • 运行结果:
    在这里插入图片描述
    注意
    在这里插入图片描述
    这是因为:只 put() 进去一个数据,而要取出两个数据,第二个 take()取不到数据,导致阻塞

3.4 阻塞队列实现等待唤醒机制

  • 案例需求

    • 生产者(Cooker): 继承 Thread 类,重写 run() 方法,设置线程任务
      • 1.构造方法中接收一个阻塞队列对象
      • 2.在 run() 方法中循环获取阻塞队列中添加包子
      • 3.打印添加剂结果
    • 消费者(Foodie): 继承 Thread 类,重写 run()方法,设置线程任务
      • 1.构造方法中接收一个阻塞队列对象
      • 2.在 run 方法中循环获取阻塞队列中的包子
      • 3.打印获取结果
    • 测试类(Demo): 里面有 main 方法, main 方法中的代码步骤如下
      • 1.创建生产者线程 和消费者线程对象,构造方法中传入阻塞队列对象
      • 2.分别开启两个线程
  • Cooker(生产者类):

public class Cooker extends Thread {private ArrayBlockingQueue<String> list;public Cooker(ArrayBlockingQueue<String> list) {this.list = list;}@Overridepublic void run() {while (true) {try {list.put("汉堡包");System.out.println("厨师放了一个汉堡包");} catch (InterruptedException e) {e.printStackTrace();}}}
}
  • Foodie(消费者)类:
public class Foodie extends  Thread {private ArrayBlockingQueue<String> list;public Foodie(ArrayBlockingQueue<String> list) {this.list = list;}@Overridepublic void run() {while (true) {try {String take = list.take();System.out.println("吃货从队列中获取了"+take);} catch (InterruptedException e) {e.printStackTrace();}}}
}
  • Demo(测试)类:
public class Demo {public static void main(String[] args) {//创建一个阻塞队列,容量为1ArrayBlockingQueue<String> list = new ArrayBlockingQueue<>(1);//创建线程并开启Cooker c = new Cooker(list);Foodie f = new Foodie(list);c.start();f.start();}
}
  • 运行结果:
    在这里插入图片描述
    注意

  • 我们设置的阻塞队列的容量为1,正常情况下应该存一个,取一个,但是为什么会出现同一种情况连续出现两次呢?

    • 可能这时我们会考虑到是没有上锁导致两个线程抢CPU执行权的问题
  • 有了这个思考,我们可以来查看 put 方法 和 take 方法的源码

    • put 方法的源码
      在这里插入图片描述
  • take 方法的源码
    在这里插入图片描述

  • 从源码中我们可以看见,两个方法底层都已经实现了上锁,但是为什么会出现一种情况连续出现两次呢?

    • 这是因为两条输出语句是我们自己写的,并没有在锁里边,所以会出现上边这种情况。

总结

以上是今天的全部内容,详细介绍了生产者和消费者模式,并用具体的例子帮助读者理解这种模式,也介绍了阻塞队列的基本使用,以及阻塞队列实现等待唤醒机制并分析了出现的问题。希望大家多多关注在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用OpenCV的函数hconcat()、vconcat()实现图像或矩阵的连接

使用OpenCV的函数hconcat()、vconcat()实现图像或矩阵的连接 函数hconcat()在水平方向上连接图像或矩阵&#xff1b; 函数vconcat()在垂直方向上连接图像或矩阵。 两个函数的原型和使用方法一模一样&#xff0c;所以在下面的函数原型介绍中&#xff0c;只介绍函数hconcat()的…

人工智能:声纹相关基础概念介绍

❤️作者主页&#xff1a;IT技术分享社区 ❤️作者简介&#xff1a;大家好,我是IT技术分享社区的博主&#xff0c;从事C#、Java开发九年&#xff0c;对数据库、C#、Java、前端、运维、电脑技巧等经验丰富。 ❤️个人荣誉&#xff1a; 数据库领域优质创作者&#x1f3c6;&#x…

软件测试工程师涨薪攻略!3年如何达到30K!

1.软件测试如何实现涨薪 首先涨薪并不是从8000涨到9000这种涨薪&#xff0c;而是从8000涨到15K加到25K的涨薪。基本上三年之内就可以实现。 如果我们只是普通的有应届毕业生或者是普通本科那我们就只能从小公司开始慢慢往上走。 有些同学想去做测试&#xff0c;是希望能够日…

[附源码]计算机毕业设计设备运维平台出入库模块APPSpringboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【数据结构】建堆的方式、堆排序以及TopK问题

建堆的方式、堆排序以及TopK问题1、建堆的两种方式1.1 向上调整建堆1.2 向下调整建堆2、堆排序3、TopK问题4、建堆、堆排序、TopK问题全部代码1、建堆的两种方式 我们知道&#xff0c;堆是二叉树的一种&#xff0c;二叉树的建立是借助结构体与数组完成的&#xff08;通过在结构…

Java IO流(详解)

1. File1. 创建2. 操作1. 获取文件信息2. 目录创建/删除2. IO流1. FileInputStream1. 简单使用2. 读取中文2. FileOutputStream1. 简单使用2. 追加写入3. 文件拷贝4. FileReader1. 简单使用2. 提高读取速度5. FileWriter1. 简单使用6. 节点流和处理流简介7. BufferedReader1. 简…

yolo后处理操作-如何获取我们想要的目标框及置信度?

yolo后处理就是模型的输出进行处理&#xff0c;得到我们想要的坐标框的xywhxywhxywh以及confidenceconfidenceconfidence 学习笔记 这是yolov1的模型&#xff0c;他将图像划分成了7x7个网格&#xff0c;每个网格负责预测两个边界框&#xff0c;每个边界框都有5个信息$x、y、w、…

腾讯云年终选购云服务器攻略!

随着云计算的快速发展&#xff0c;很多用户都选择上云&#xff0c;上运中最常见的产品就是云服务器CVM和轻量应用服务器了&#xff0c;那么怎么选购最优惠呢&#xff0c;这篇文章将介绍新老用户选购腾讯云服务器的几个优惠方法。 一、买赠专区 第一个介绍的就是买赠专区&…

MySQL下载安装运行

方式1、MySQL 官方网站&#xff1a;http://www.mysql.com 拉到最下面&#xff1a; 方式2、Windows版 MySQL 的官方下载地址&#xff1a;https://dev.mysql.com/downloads/mysql/ 配置环境变量&#xff1a;在Path中添加至“\bin”&#xff08;系统盘C盘&#xff09;形式 使用管…

(02)Cartographer源码无死角解析-(33) LocalTrajectoryBuilder2D: 点云数据流向、处理、消息发布等→流程复盘

讲解关于slam一系列文章汇总链接:史上最全slam从零开始&#xff0c;针对于本栏目讲解(02)Cartographer源码无死角解析-链接如下: (02)Cartographer源码无死角解析- (00)目录_最新无死角讲解&#xff1a;https://blog.csdn.net/weixin_43013761/article/details/127350885 文末…

我国跨国企业外汇风险管理——以海尔公司为例

目 录 摘 要 I 一、 绪论 1 &#xff08;一&#xff09; 选题背景及意义 1 &#xff08;二&#xff09; 国内研究现状 1 1&#xff0e; 国外研究现状 1 2&#xff0e; 国内研究现状 3 &#xff08;三&#xff09; 研究内容及方法 3 &#xff08;四&#xff09; 跨国企业外汇风险…

java+mysql基于SSM的大学生兼职信息系统-计算机毕业设计

开发环境 运行环境&#xff1a; 开发工具:IDEA /Eclipse 数据库:MYSQL5.7 应用服务:Tomcat7/Tomcat8 使用框架:SSM(springspringMVCmybatis)vue 项目介绍 论文主要是对大学生兼职信息系统进行了介绍&#xff0c;包括研究的现状&#xff0c;还有涉及的开发背景&#xff0c;然…

构建全真互联数字地图底座 腾讯地图产业版WeMap重磅升级

前言 &#xff1a;伴随着地理信息产业的不断演进&#xff0c;以及LBS、大数据、5G、云、AI等新技术的持续应用&#xff0c;数实融合发展呈现出加速态势&#xff0c;数字地图也从移动互联网时代向产业互联网时代进化。 WeMap腾讯地图产业版重磅升级&#xff01;12月1日&#xff…

Python解题 - CSDN周赛第12期 - 蚂蚁的烦恼

问哥本期有幸all pass&#xff0c;而且用时50分钟内。不过回想起来&#xff0c;本期的四道题目的设计都或多或少不太严谨&#xff0c;或者说测试用例不够全面&#xff08;后面会细说&#xff09;。这样的话就极有可能造成虽然通过了测试&#xff0c;拿到了分数&#xff0c;但代…

数据结构—链表

文章目录链表&#xff08;头插法、尾插法、单链表反转&#xff09;二分查找算法&#xff1a;哈夫曼编码构建链表insert()创建链表&#x1f447;【1】尾插法【2】头插法【3】遍历输出链表【4】输出链表的长度【5】查找链表上是否有该元素【6】指定位置插入数据链表经典面试题【1…

12家硬件厂商发布飞桨生态发行版 软硬一体协同发展

11月30日&#xff0c;由深度学习技术及应用国家工程研究中心主办、百度飞桨承办的WAVE SUMMIT2022深度学习开发者峰会如期举行。峰会上&#xff0c;百度AI技术生态总经理马艳军发布了飞桨深度学习平台的最新技术和生态进展&#xff0c;全新发布飞桨开源框架2.4版本&#xff0c;…

【论文简述】 Point-MVSNet:Point-Based Multi-View Stereo Network(ICCV 2019)

一、论文简述 1. 第一作者&#xff1a;Rui Chen、Songfang Han 2. 发表年份&#xff1a;2019 3. 发表期刊&#xff1a;ICCV 4. 关键词&#xff1a;MVS、深度学习、点云、迭代改进 5. 探索动机&#xff1a;很多传统方法通过多视图光度一致性和正则化优化迭代更新&#xff…

【Java实战】大厂都是怎样进行单元测试的

目录 一、前言 二、单元测试 1.【强制】好的单元测试必须遵守 AIR 原则。 2.【强制】单元测试应该是全自动执行的&#xff0c;并且非交互式的。测试用例通常是被定期执行的&#xff0c;执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。不…

清华、北大、中科大、UMA、MSU五位博士生畅聊深度学习理论

点击蓝字关注我们AI TIME欢迎每一位AI爱好者的加入&#xff01;伴随着深度学习的蓬勃发展&#xff0c;进入人们视线的好像都是算法或AlphaGo等应用层面的东西。但是在理论上&#xff0c;深度学习似乎却没有很出圈的相关理论。因此&#xff0c;部分人也在批评深度学习是缺乏理论…

蓝海创意云·11月大事记 || 12月,暖心相伴

秋尽冬生&#xff0c;日短天寒 告别了立冬与小雪 时光不紧不慢开启了新一月的篇章 万物冬藏&#xff0c;沉淀酝酿 站在十二月的路口 蛰伏打磨&#xff0c;静待厚积而薄发 导 读 ● 客户端更新&#xff1a;新增PSD通道合成选项 ● 渲染案例&#xff1a;绝代双骄重启江湖…