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

阻塞队列:

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

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

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

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

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

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

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使用

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

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

相关文章

1Panel应用推荐:WordPress开源博客软件和内容管理系统

1Panel(github.com/1Panel-dev/1Panel)是一款现代化、开源的Linux服务器运维管理面板,它致力于通过开源的方式,帮助用户简化建站与运维管理流程。为了方便广大用户快捷安装部署相关软件应用,1Panel特别开通应用商店&am…

计算机毕业设计Tensorflow+LSTM空气质量监测及预测系统 天气预测系统 Spark Hadoop 深度学习 机器学习 人工智能

温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 温馨提示:文末有 CSDN 平台官方提供的学长联系方式的名片! 作者简介:Java领…

语言月赛 202308【小粉兔做麻辣兔头】题解(AC)

》》》点我查看「视频」详解》》》 [语言月赛 202308] 小粉兔做麻辣兔头 题目描述 粉兔喜欢吃麻辣兔头,麻辣兔头的辣度分为若干级,用数字表示,数字越大,兔头越辣。为了庆祝粉兔专题赛 #1 的顺利举行,粉兔要做一些麻…

激活函数篇 02 —— 双曲正切函数tanh

本篇文章收录于专栏【机器学习】 以下是激活函数系列的相关的所有内容: 一文搞懂激活函数在神经网络中的关键作用 逻辑回归:Sigmoid函数在分类问题中的应用 tanh ⁡ ( x ) e x − e − x e x e − x \tanh(x)\frac{e^x - e^{-x}}{e^x e^{-x}} tanh(x)exe−xex…

STM32G0B1 ADC DMA normal

目标 ADC 5个通道,希望每1秒采集一遍; CUBEMX 配置 添加代码 #define ADC1_CHANNEL_CNT 5 //采样通道数 #define ADC1_CHANNEL_FRE 3 //单个通道采样次数,用来取平均值 uint16_t adc1_val_buf[ADC1_CHANNEL_CNT*ADC1_CHANNEL_FRE]; //传递…

【数据结构】链表应用1

链表应用 面试题 02.02.返回倒数第k个节点题目描述思路解题过程复杂度 查找相同后缀题目描述解题思路完整代码: 删除绝对值相等的节点题目描述解题思路代码 面试题 02.02.返回倒数第k个节点 题目描述 实现一种算法,找出单向链表中倒数第 k 个节点。返回…

【JVM详解一】类加载过程与内存区域划分

一、简介 1.1 概述 JVM是Java Virtual Machine(Java虚拟机)的缩写,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。由一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域等组成。JVM屏蔽了与操作系统平台相关…

基于STM32设计的仓库环境监测与预警系统

目录 项目开发背景设计实现的功能项目硬件模块组成设计思路系统功能总结使用的模块的技术详情介绍总结 1. 项目开发背景 随着工业化和现代化的进程,尤其是在制造业、食品业、医药业等行业,仓库环境的监控和管理成为了至关重要的一环。尤其是在存储易腐…

“可通过HTTP获取远端WWW服务信息”漏洞修复

环境说明:①操作系统:windows server;②nginx:1.27.1。 1.漏洞说明 “可通过HTTP获取远端WWW服务信息”。 修复前,在“响应标头”能看到Server信息,如下图所示: 修复后,“响应标头…

创建一个javaWeb Project

文章目录 前言一、eclipse创建web工程二、web.xmlservlet.xml< mvc:annotation-driven/ > Spring MVC 驱动< context:component - scan >&#xff1a;扫描< bean > ... < /bean >< import > config/beans.xml beans.xmlmybatis.xml 前言 javaWe…

aspectFill(填充目标区域的同时保持图像的原有宽高比 (aspect ratio)图像不会被拉伸或压缩变形

“aspectFill” 是一个常用于图像和视频处理的术语&#xff0c;尤其是在用户界面 (UI) 设计和图形编程领域。它描述的是一种图像缩放或调整大小的方式&#xff0c;旨在填充目标区域的同时保持图像的原有宽高比 (aspect ratio)。 更详细的解释: Aspect Ratio (宽高比): 指的是图…

界址点成果表批量生成(新增.docx格式)-附工具下载链接

界址点编号工具20250208更新&#xff08;成果表新增.docx格式&#xff09;。 【工具简介】工具可根据面图层&#xff0c;西北角顺时针批量生成界址点&#xff0c;可以设置角度、距离参数&#xff0c;来减少生成界址点的数量&#xff08;不用全部节点生成界址点&#xff09;。生…

《redis缓存淘汰机制》

【redis缓存淘汰机制导读】redis作为一款内存型数据库&#xff0c;其设计的初衷就是为了给广大业务层提供高效的数据读、写能力&#xff0c;因为访问内存的速度肯定是要比直接访问磁盘的速度快几个数量级&#xff0c;假设业务方所有数据读、写请求全部都转发到后台的数据库&…

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

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

深入解析AI技术原理

序言 在当今数字化时代,人工智能(AI)已经成为科技领域最炙手可热的话题之一。从智能家居到自动驾驶汽车,从医疗诊断到金融风险预测,AI的应用无处不在。然而,对于许多人来说,AI背后的技术原理仍然充满了神秘色彩。本文将深入探讨AI的核心技术原理,从基础理论到前…

计算机组成原理(3)

计算机组成原理&#xff08;3&#xff09; 存储器层次结构存储器概述存储器分类存储器性能指标 半导体随机存储SRAM和DRAM 存储器层次结构 主存-辅存&#xff1a;实现了虚拟存储系统&#xff0c;解决了主存容量不足的问题&#xff1b; Cache-主存&#xff1a;解决了主存于CPU速…

计算机网络-SSH基本原理

最近年底都在忙&#xff0c;然后这两天好点抽空更新一下。前面基本把常见的VPN都学习了一遍&#xff0c;后面的内容应该又继续深入一点。 一、SSH简介 SSH&#xff08;Secure Shell&#xff0c;安全外壳协议&#xff09;是一种用于在不安全网络上进行安全远程登录和实现其他安…

【理论知识】 2D 卷积、3D 卷积与 3D 池化

摘要 卷积神经网络&#xff08;Convolutional Neural Networks, CNNs&#xff09;在计算机视觉、视频处理和医学影像分析等领域取得了显著的成功。卷积操作作为CNN的核心&#xff0c;主要包括二维卷积&#xff08;2D Convolution&#xff09;、三维卷积&#xff08;3D Convolu…

apisix网关ip-restriction插件使用说明

ip-restriction插件可以在网关层进行客户端请求ip拦截。 当然了&#xff0c;一般不推荐使用该方法&#xff0c;专业的事专业工具做。建议有条件&#xff0c;还是上防火墙或者waf来做。 官方文档&#xff1a;ip-restriction | Apache APISIX -- Cloud-Native API Gateway whit…

uniapp 编译生成鸿蒙正式app步骤

1&#xff0c;在最新版本DevEco-Studio工具新建一个空项目并生成p12和csr文件&#xff08;构建-生成私钥和证书请求文件&#xff09; 2&#xff0c;华为开发者平台 根据上面生成的csr文件新增cer和p7b文件&#xff0c;分发布和测试 3&#xff0c;在最新版本DevEco-Studio工具 文…