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

news2024/11/19 15:16:00

文章目录

  • 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.叫醒等待的消费者开吃
        */
    @Override
    public 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.判断,共享数据是否结束,没有结束
    @Override
    public 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;
    }

    @Override
    public 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 {
        //阻塞队列的基本用法

        //创建阻塞队列的对象,并规定里边的容量为1
        ArrayBlockingQueue<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;
    }

    @Override
    public 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;
    }

    @Override
    public 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) {
        //创建一个阻塞队列,容量为1
        ArrayBlockingQueue<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.coloradmin.cn/o/62752.html

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

相关文章

SQL SERVER数据库修复之REPAIR_ALLOW_DATA_LOSS级别简介和实例

目录 &#xff08;一&#xff09;前言 &#xff08;二&#xff09;关于DBCC CHECKxxx系列命令 1. DBCC CHECKALLOC 2. DBCC CHECKCATALOG 3. DBCC CHECKDB &#xff08;1&#xff09;基本概念 &#xff08;2&#xff09;基本语法 &#xff08;3&#xff09;参数说明 &…

分析常见限流算法及手写三种(计数器、漏斗、令牌桶)代码实现

常见的限流算法分析 限流在我们日常生活中经常见到&#xff0c;如火车站门口的栏杆、一些景点的门票只出售一定的数量 等等。在我们的开发中也用到了这种思想。 为什么要限流 在保证可用的情况下尽可能多增加进入的人数,其余的人在排队等待,或者返回友好提示,保证里面的进行…

机器如何快速学习数据采集

很多人都在思考如何利用机器学习&#xff08;ML&#xff09;算法来提高产品或服务的质量。 如果你正在考虑采用ML&#xff0c;以正确的格式收集正确的数据&#xff0c;将会降低你的数据清理工作以及数据浪费。 要收集所有数据 收集所有数据是非常重要的。除非你真正训练一个…

Excel基于分隔符拆分列

1、示例数据 id name describe 1 张三 学生 2 李四 老师 3 王五 学生 2、将数据复制到Excel中 数据目前都在A列中 3、将数据一次拆分到多个列 Excel基于分隔符拆分列&#xff0c;将数据一次拆分到多个列。 选中数据&#xff0c;数据-分列-分列 设置分隔符 点击完成后&…

【Python自然语言处理】使用逻辑回归(logistic)对电影评论情感分析实战(超详细 附源码)

需要源码和数据集请点赞关注收藏后评论区留言私信~~~ 一、舆情分析 舆情分析很多情况下涉及到用户的情感分析&#xff0c;或者亦称为观点挖掘&#xff0c;是指用自然语言处理技术、文本挖掘以及计算机语言学等方法来正确识别和提取文本素材中的主观信息&#xff0c;通过对带有…

使用Visual Studio Code 进行Python编程(一)

1、下载Visual Studio Code 到微软的Visual Studio Code官方主页下载Visual Studio Code: Visual Studio: 面向软件开发人员和 Teams 的 IDE 和代码编辑器Visual Studio 开发工具和服务让任何开发人员在任何平台和语言的应用开发都更加轻松。 随时随地免费使用代码编辑器或 I…

Spire.Office for .NET 7.12.0 2022年最后版本?

谷歌能找到破解版是破坏强签名&#xff0c;不能用web&#xff0c;请把大家不要用Spire.Office for .NET is a combination of Enterprise-Level Office .NET API offered by E-iceblue. It includes Spire.Doc, Spire.XLS, Spire.Spreadsheet, Spire.Presentation, Spire.PDF, …

数据库开发项目 flask + html 01

目的 开放平台&#xff08;网站&#xff09; 前端开发 HTML CSS JavaScript Web框架&#xff1a; 接受请求并处理 MySQL数据库&#xff1a; 存储数据 快速上手&#xff1a; 基于 Flask Web框架 快速搭建网站。 进阶&#xff1a; 基于 Django框架 1. 快速开发网站 安装框架 …

(附源码)SSM介绍信智能实现系统 毕业设计 260930

SSM介绍信智能实现系统 摘 要 科技进步的飞速发展引起人们日常生活的巨大变化&#xff0c;电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流&#xff0c;人类发展的历史正进入一个新时代。在现实运用中&…

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

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

分享几款免费实用的国产内网穿透工具

对于没有公网IP的用户来说&#xff0c;如何实现远程管理或让局域网的服务可以被公网访问到是一个问题。当然&#xff0c;也有很多类似的需求&#xff0c;比如&#xff1a; 微信公众号小程序开发调试公网访问本地web项目异地远程处理公司服务问题异地访问公司内网财务/管理系统…

Qt 中模型视图编程的基本概念

背景 一个应用程序本质可以抽象为三部分&#xff1a;界面、逻辑处理、数据。程序中存储有大量的数据&#xff0c;经过逻辑处理后、通过界面展示给用户&#xff0c;同时用户可以通过界面对数据进行编辑&#xff0c;如下图所示&#xff1a; Qt 中的模型视图架构就是用来实现大量…

Spring_第3章_AOP+事务

Spring_第3章_AOP事务 文章目录Spring_第3章_AOP事务一、AOP1 AOP简介问题导入1.1 AOP简介和作用【理解】1.2 AOP中的核心概念【理解】2 AOP入门案例【重点】问题导入2.1 AOP入门案例思路分析2.2 AOP入门案例实现【第一步】导入aop相关坐标【第二步】定义dao接口与实现类【第三…

8 常规聚类

常规聚类 聚类分析是解决数据全方位自动分组的有效方式。若将数据全体视为一个大类&#xff0c;这个大类很可能是由若干个包含了一定数量观测的自然小类”组成的。聚类分析的目的就是找到这些隐藏于数据中的客观存在的“自然小类”&#xff0c;并通过刻画“自然小类”体现数据…

舆情监控软件

随着中国互联网的快速发展&#xff0c;舆情监测成为工作中的一部分&#xff0c;如果没有舆情监控软件的及时介入&#xff0c;负面舆情将会迅速扩大并蔓延到各个方面&#xff0c;对社会以及公众造成严重的影响&#xff0c;舆情监控软件对企业政府有着深远影响&#xff0c;接下来…

Python学习小组课程P5-Python办公(2)Excel读取与Word生成

一、前言 注意&#xff1a;此为内部小组学习资料&#xff0c;非售卖品&#xff0c;仅供学习参考。 本系列课程&#xff1a; Python学习小组课程-课程大纲与Python开发环境安装 Python学习小组课程P1-Python基础&#xff08;1&#xff09;语法与数组 Python学习小组课程P2-Pyth…

【配电网重构】基于yalmip求解含sop+二阶锥配电网重构附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

ouster-32激光雷达使用---雷达输出数据分析

ouster-32激光雷达使用---雷达输出数据分析雷达输出数据分析所有数据imu数据雷达数据坐标系Rviz显示雷达输出数据分析 所有数据 查看当前topic消息种类 rostopic list终端输出 /clicked_point /initialpose /move_base_simple/goal /os_node/imu_packets /os_node/lidar_pa…

ADSP-21489的开发详解:VDSP+自己编程写代码开发(2-软件和硬件的开发环境搭建)

Visual DSP软件的安装 运行 setup 软件安装包&#xff0c;全部下一步即可完成软件安装&#xff0c;非常简单。我们的资料里提供了 VDSP5.1.2 软件&#xff0c;当然您也可以通过 ADI 公司官网下载。 VDSP5.1.2 软件官网下载地址&#xff1a; Visual DSP5.1.2的ADI官网下载链接…

2022深入学习C++教程

2022深入学习C教程 课堂和实践课程 – C 11 的功能、异常处理和 STL – 适用于学术界和工业界 课程英文名&#xff1a;Learn C Programming -Beginner to Advance- Deep Dive in C 此视频教程共30.0小时&#xff0c;中英双语字幕&#xff0c;画质清晰无水印&#xff0c;源码…