设计模式-生产者消费者模型

news2025/2/9 7:25:57

阻塞队列:

在介绍生产消费者模型之前,我们先认识一下阻塞队列。

阻塞队列是一种支持阻塞操作的队列,常用于生产者消费者模型,它提供了线程安全的队列操作,并且在队列为空或满时,能够阻塞等待,直到条件满足时。

阻塞队列:是一种更为复杂的队列,和之前学的队列有些相似。

阻塞队列的特点:阻塞队列是线程安全的

当队列为空时,尝试出队列,出队列操作就会形成阻塞,直到添加元素为止。

当队列为满时,尝试入队列,入队列操作就会形成阻塞,直到其他线程取走元素为止。

java阻塞队列主要通过java.util.concurrent包中的BlockingQueue接口及其实现类来实现,常见的实现类包括:

1.ArrayBlockingQueue:基于数组的有界阻塞队列,必须指定队列的容量,按照先进先出原则处理元素

 2.LinkedBlockingQueue:基于链表的阻塞队列,可以选择有界或无界的(默认无界的【非常大】),按照先进先出原则处理元素。(实际开发,一般建议需要设置好上限,否则你的队列可能非常大,容易把资源耗尽,产生内存超出范围这样的异常)

3.PriorityBlockingQueue:基于优先级的无界阻塞队列,元素必须实现Comparable接口,或者通过构造函数传入Comparator,按照优先级顺序处理元素。

 插入操作:add(E e):插入元素,成功返回true,队列满时抛出IllegalStateException(但由于是无界队列,通常不会满),还有offer(E e),put(E e)

移除操作:remove():移除元素,并返回队列的头部元素,队列为空则抛出异常。poll():移除并返回头部的元素,若队列为空,则返回null。take()移除并返回头部的元素,队列为空时阻塞,直到有元素可用。

在入队列和出队列时,只有put()方法和take()方法,才带有阻塞功能。

生产者-消费者模型:

 生产者消费者模型是一种经典的多线程协作模式,用于解决生产者和消费者之间的数据交换问题。生产者负责生成数据并放入共享缓冲区(一般用上述的阻塞队列来储存),而消费者则从缓冲区中取出数据进行处理。为了避免竞争条件和确保线程安全,通常需要使用同步机制。

 示例:平时我们包饺子的时候:

 第一种情况:擀饺子皮的人(生产者)直接将擀好的饺子皮递给包饺子的人(消费者),这样子的缺点很明显:如果翰饺子皮的人的速度很快,包饺子的人速度跟不上,那饺子皮就会有过剩的,怎么办呢?

如果翰饺子皮的人将擀好的饺子皮放在桌子上,包饺子的人直接从桌子上面拿饺子皮来包饺子,这样就不会出现上面的问题(也就是生产者消费者模型)

在生产者消费者模型使用阻塞队列的优势:

1.解耦合 :

解耦合不一定是两个线程之间,也可以是两个服务器之间

 如果服务器A直接访问服务器B,那么这两个服务器之间的耦合度就更高。编写服务器A的代码会有一些包含服务器B的相关逻辑,编写服务器B的代码多少会包含一些服务器A的相关逻辑。当一个服务器受到影响,另一个服务器也会受到相应的影响(如果耦合度很高,这样就不是很好修改相关代码)

引入阻塞队列之后,服务器A和队列交互,服务器B和队列交互,服务器A不会直接和服务器B交互

,就降低了服务器A和B之间的耦合度。 

2.削峰填谷:

 上述情况,像服务器A这种上游服务器(入口服务器),干的活很少(单个请求消耗的资源很少)但是B这种下游服务器,承担着更重的任务,复杂的计算/储存工作,单个请求消耗的资源很多(更加容易挂)

一般流量激增的时间是突发的,也是短暂的,为了让服务器B即不会突发性的面临流量激增,也还能处理请求,所以就可以通过阻塞队列来充当缓冲区(趁着波峰过去了,B继续处理请求,利用波谷的时间,来处理之前积压的数据)

阻塞队列很重要,有的甚至会把队列单独部署成一个服务,队列服务器往往可以抵抗很高的请求量。

生产者消费者模型的代价:

1.引入队列之后,整体的结构会更加复杂 (此时需要更多的机器,进行部署,生产环境的结构会更加复杂,管理起来会更麻烦)

2.效率会有影响

模拟实现一个简单的阻塞队列:

class MyBlockingQueue{
private String[] data=null;
private int head=0;//队列头
private int tail=0;//队列尾
private int size=0;//元素个数
public MyBlockingQueue(int capacity){
    data=new String[capacity];
 }
public void put(String elem) throws InterruptedException {
    synchronized (this){
        while(size>=data.length){
            //队列满了,队列未满时,唤醒wait
            this.wait();
        }
        data[tail]=elem;
        tail++;
        if(tail>data.length){
            tail=0;
        }
        size++;
        this.notify();
    }
}
public String take() throws InterruptedException {
    synchronized (this){
        while(size==0){
            //队列为空,队列不为空时,唤醒wait
            this.wait();
        }
        String s=data[head];
        head++;
        if(head>data.length){
            head=0;
        }
        size--;
        this.notify();
        return s;
    }
}
}
public class Demo {
    public static void main(String[] args) {
        MyBlockingQueue queue=new MyBlockingQueue(1000);
        Thread producer=new Thread(()->{
           int n=0;
           while(true){
               try {
                   queue.put(n+"");
                   System.out.println("生产元素 " + n);
                   n++;
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        Thread consumer=new Thread(()->{
            while(true){
                String n=null;
                try {
                    n=queue.take();
                    System.out.println("消耗元素 " + n);
                    Thread.sleep(1000);//看到结果的变化
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });
        producer.start();
        consumer.start();
    }
}

如果有若干个线程使用这个队列,发生阻塞只有两种可能,要么所有线程阻塞在put方法,要么所有线程阻塞在take方法,但是这些线程不可能既阻塞在put,又阻塞在take里面(因为队列不可能同时为满,又为空)

问题1:为什么这里要用while循环呢?不用if?

答:这里用while循环,是为了“二次验证”,因为wait除了会被notify唤醒之外,还有可能interrupt这样的方法给中断,用if判断,就有可能有提前唤醒的风险。所以用while进行二次验证。

问题2:如果此时队列已经满了,此时三个线程分别put(1),put(2),put(3),那这三个线程都会阻塞,现在第四个线程take()之后,线程1的put(1)的wait被唤醒,继续执行,执行到线程1的notify,因为唤醒是随机的,那有没有可能唤醒线程2的put(2),或者线程3的put(3)。

答:不可能唤醒线程2,线程3,多线程notify对wait的唤醒是随机的,但是此时如果唤醒了线程2/线程3的wait,但是别忘了还有二次验证,当验证发现阻塞队列已经满了,还是会继续阻塞等待。

 注意:wait在被设计的时候,就是搭配while使用

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

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

相关文章

RabbitMQ介绍以及基本使用

文章目录 一、什么是消息队列? 二、消息队列的作用(优点) 1、解耦 2、流量削峰 3、异步 4、顺序性 三、RabbitMQ基本结构 四、RabbitMQ队列模式 1、简单队列模式 2、工作队列模式 3、发布/订阅模式 4、路由模式 5、主题模式 6、…

嵌入式硬件篇---OpenMV的硬件流和软件流

文章目录 前言一、硬件流控制(Hardware Flow Control)1. 基本原理RTSCTS 2. OpenMV中的实现• 硬件要求• 代码配置• 工作流程 二、软件流控制(Software Flow Control)1. 基本原理XONXOFF 2. OpenMV中的实现• 代码配置• 工作流…

【AIGC提示词系统】基于 DeepSeek R1 + ClaudeAI 易经占卜系统

上篇因为是VIP,这篇来一个免费的 提示词在最下方,喜欢的点个关注吧 引言 在人工智能与传统文化交融的今天,如何让AI系统能够传递传统易经文化的智慧,同时保持易经本身的神秘感和权威性,是一个极具挑战性的课题。本文将…

OpenAI 实战进阶教程 - 第十节 : 结合第三方工具的向量数据库Pinecone

面向读者群体 本节课程主要面向有一定编程基础和数据处理经验的计算机从业人员,如后端开发工程师、数据工程师以及对 AI 应用有浓厚兴趣的技术人员。即使你之前没使用过向量数据库,也可以通过本节的实操内容快速上手,为企业或个人项目构建强…

深入Linux系列之进程地址空间

深入Linux系列之进程地址空间 1.引入 那么在之前的学习中,我们知道我们创建一个子进程的话,我们可以在代码层面调用fork函数来创建我们的子进程,那么fork函数的返回值根据我们当前所处进程的上下文是返回不同的值,它在父进程中返…

AWK系统学习指南:从文本处理到数据分析的终极武器 介绍

目录 一、AWK核心设计哲学解析 1.1 记录与字段的原子模型 1.2 模式-动作范式 二、AWK编程语言深度解析 2.1 控制结构 说明: 2.2 关联数组 代码说明: 示例输入和输出: 注意事项: 2.3 内置函数库 三、高级应用技巧 3.1…

250207-MacOS修改Ollama模型下载及运行的路径

在 macOS 上,Ollama 默认将模型存储在 ~/.ollama/models 目录。如果您希望更改模型的存储路径,可以通过设置环境变量 OLLAMA_MODELS 来实现。具体步骤如下: 选择新的模型存储目录:首先,确定您希望存储模型的目标目录路…

半导体行业跨网文件交换系统

在当今这个数字化转型的时代,半导体行业作为技术密集型产业,正面临着前所未有的信息安全挑战。随着企业内外网隔离措施的加强,如何实现既安全又高效的跨网文件交换,成为了众多半导体企业的一大难题。 特别是在研发和生产过程中产生…

使用GD32F470的硬件SPI读写W25Q64

代码简单改下引脚定义便可以使用! 使用的单片机具体型号:GD32F470ZGT6 简单介绍下W25Q64: /* W25Q64 性能参数 */ /* 容量:8MByte 64Mbit */ /* 有128个块,每个块有64KByte */ /* 每个块有16个扇区,每个…

02为什么 OD门和 OC门输出必须加上拉电阻?

为什么 OD(开漏)门和 OC(开集)门输出必须加上拉电阻? 1、首先一点,知道OD是说的MOS管,OC是说的三极管,二者的区别与联系大家应该都懂。 2、以OC门举例,芯片的OC门内部结…

AI方案调研与实践 (不定期补充)

目录 说明 1. AI云主机准备 1.1 Ollama配置 设置模型保存路径 配置模型驻留内存时间 查看GPU状况命令: nvidia-smi 2. Deepseek 2.1 安装与使用 3. LobeChat配置 参考 说明 调研并实例化各种AI方案,探索训练/使用方式的最佳实践。 1. AI云主机准备 可以去一…

人工智能大模型之模型蒸馏与知识蒸馏

一、背景介绍 随着人工智能技术的不断发展,大模型在各个领域的应用也越来越广泛。模型蒸馏(Model Distillation)和知识蒸馏(Knowledge Distillation)是两种非常重要的模型压缩技术,它们可以帮助我们将大型…

[手机Linux] onepluse6T 系统重新分区

一,刷入TWRP 1. 电脑下载 Fastboot 工具(解压备用)和对应机型 TWRP(.img 后缀文件,将其放入前面解压的文件夹里) 或者直接这里下载:TWRP 2. 将手机关机,长按音量上和下键 开机键 进入 fastbo…

k8s部署elasticsearch

前置环境:已部署k8s集群,ip地址为 192.168.10.1~192.168.10.5,总共5台机器。 1. 创建provisioner制备器(如果已存在,则不需要) 制备器的具体部署方式,参考我之前的文章:k8s部署rab…

本地部署DeepSeek

下载Docker Docker Desktop: The #1 Containerization Tool for Developers | Docker 下载安装ollama Download Ollama on macOS 下载完成后解压运行 终端输入 Ollama --version 输出对应版本号即为下载成功 如果没有弹出上述图片,浏览器输入http://localhos…

21.[前端开发]Day21-HTML5新增内容-CSS函数-BFC-媒体查询

王者荣耀-网页缩小的问题处理 为什么会产生这个问题?怎么去解决 可以给body设置最小宽度 1 HTML5新增元素 HTML5语义化元素 HTML5其他新增元素 2 Video、Audio元素 HTML5新增元素 - video video支持的视频格式 video的兼容性写法 HTML5新增元素 - audio audio…

nbmade-boot调用deepseek的api过程与显示

希望大家一起能参与我的新开源项目nbmade-boot: 宁波智能制造低代码实训平台 下面简单介绍调用最近大红的AI :deepseek的api过程与显示,包括前后端代码与效果图 一、后端代码 1、几个基础的java类 DeepSeekRequest .java package com.nbcio.demo.do…

Linux:安装 node 及 nvm node 版本管理工具(ubuntu )

目录 方法一:手动下载安装文件安装方法二:curl安装 方法一:手动下载安装文件安装 git clone 远程镜像 git clone https://gitee.com/mirrors/nvm安装 nvm bash install.sh刷新配置,使配置在终端生效 // 方法 1 source /root/.…

【多线程-第三天-NSOperation和GCD的区别 Objective-C语言】

一、我们来看NSOperation和GCD的区别 1.我们来对比一下,NSOperation和GCD, 那这个代码,我们都写过了, 我们来看一下它们的特点啊,首先来看GCD, 1)GCD是C语言的框架,是iOS4.0之后推出的,并且它的特点是,针对多核做了优化,可以充分利用CPU的多核,OK,这是GCD, 2…

【医院运营统计专题】2.运营统计:医院管理的“智慧大脑”

医院成本核算、绩效管理、运营统计、内部控制、管理会计专题索引 引言 在当今医疗行业快速发展的背景下,医院运营管理的科学性和有效性成为了决定医院竞争力和可持续发展能力的关键因素。运营统计作为医院管理的重要工具,通过对医院各类数据的收集、整理、分析和解读,为医…