<JavaEE> 经典设计模式之 -- 使用阻塞队列实现“生产者-消费者模型”

news2024/11/29 23:33:37

目录

一、阻塞队列和“生产者-消费者模型”之间的关系

二、标准库提供了阻塞队列

三、实现自己的阻塞队列

3.1 基于数组实现普通的环形队列

3.2 将上述代码改造为线程安全

3.3 增加阻塞功能

四、使用阻塞队列实现“生产者-消费者模型”


一、阻塞队列和“生产者-消费者模型”之间的关系

1)什么是阻塞队列?

队列是一种“先进先出”的数据结构,阻塞队列是一种带有阻塞功能、线程安全的队列。

当队列满时,继续入队列则会阻塞,直到队列不为满时,才会继续入队列。

当队列空时,继续出队列则会阻塞,直到队列不为空时,才会继续出队列。

2)什么是“生产者-消费者模型”?

“生产者-消费者模型”是一种经典的开发模型,是阻塞队列的典型应用场景。

“生产者-消费者模型”主要由生产者-容器(阻塞队列)-消费者构成。

3)阻塞队列在“生产者-消费者模型”中的作用
<1>

阻塞队列可以让生产者和消费者之间解耦合。

使用阻塞队列后,生产者和消费者之间不再直接通信,而是通过阻塞队列进行沟通。

<2>阻塞队列相当于生产者和消费者之间的缓冲区,可以平衡“生产速度”和“消费速度”。

图示演示“生产者-消费者模型”:


二、标准库提供了阻塞队列

1)标准库提供了哪些阻塞队列?

在 Java 标准库中内置了阻塞队列 —— BlockingQueue 。 

BlockingQueue 是一个接口,接口的实现类包括 ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue 等。

2)BlockingQueue 的常用方法

常用方法有:

put() 方法,带阻塞功能,用于入队列。

take() 方法,带阻塞功能,用于出队列。

BlockingQueue 也提供了 offer()、poll()、peek() 等方法,但这些方法没有阻塞功能。

三、实现自己的阻塞队列

3.1 基于数组实现普通的环形队列

基于数组实现普通的环形队列

队列中应至少包括以下内容:

一个用于存放元素的数组 elems
指向队首元素的索引 head
指向队尾元素的索引 tail
用于记录队列内有效元素个数的 size
用于构造对象,参数为数组容量的构造方法
用于入队列的 put() 方法
用于出队列的 take() 方法

代码演示基于数组实现普通的环形队列:

class MyBlockingQueue {
    //数组存放元素;
    private String[] elems = null;
    //头节点;
    private int head = 0;
    //尾节点;
    private int tail = 0;
    //队列内有效元素个数;
    private int size = 0;

    //参数为数组容量的构造方法;
    public MyBlockingQueue(int capacity){
        elems = new String[capacity];
    }

    //入队列方法;
    public void put(String elem){
        if(size == elems.length){
            return;
        }
        elems[tail] = elem;
        tail++;
        if(tail >= elems.length){
            tail = 0;
        }
        size++;
    }
    
    //出队列方法;
    public String take(){
        if(size == 0){
            return "";
        }
        String elem = elems[head];
        elems[head] = null;
        head++;
        if(head >= elems.length){
            head = 0;
        }
        size--;
        return elem;
    }
}

3.2 将上述代码改造为线程安全

将上述代码改造为线程安全

以 put() 方法为例,对以下三部分进行分析:

<1> 在多线程环境下,多个线程可以同时修改共享变量 head、tail、size 等,因此需要使用 volatile 对共享变量进行修饰,保证其内存可见性。

<2> 图示分析“写操作”:

<3> 图示分析“读写操作非原子”:

依照上述三个部分的分析结果,可以对代码进行改造,改造结果如下:

class MyBlockingQueue {
    //数组存放元素;
    private String[] elems = null;
    //头节点;
    private volatile int head = 0;
    //尾节点;
    private volatile int tail = 0;
    //队列内有效元素个数;
    private volatile int size = 0;

    public MyBlockingQueue(int capacity){
        elems = new String[capacity];
    }

    //入队列方法;
    public void put(String elem){
        //加锁;
        synchronized (this){
            if(size == elems.length){
                return;
            }
            elems[tail] = elem;
            tail++;
            if(tail >= elems.length){
                tail = 0;
            }
            size++;
        }
    }

    //出队列方法;
    public String take(){
        String elem = null;
        //加锁;
        synchronized (this){
            if(size == 0){
                return "";
            }
            elem = elems[head];
            elems[head] = null;
            head++;
            if(head >= elems.length){
                head = 0;
            }
            size--;
        }
        return elem;
    }
}

3.3 增加阻塞功能

将上述代码增加阻塞功能

1)什么时候阻塞?

入队列时,如果队列已满,则线程阻塞。

出队列时,如果队列已空,则线程阻塞。

2)什么时候唤醒?

有新元素入队列,则唤醒阻塞等待的出队列线程。

有新元素出队列,则唤醒阻塞等待的入队列线程。

以 put() 方法为例,图示演示分析代码需要改动的部分:

代码演示阻塞队列:

class MyBlockingQueue {
    //数组存放元素;
    private String[] elems = null;
    //头节点;
    private volatile int head = 0;
    //尾节点;
    private volatile int tail = 0;
    //队列内有效元素个数;
    private volatile int size = 0;

    public MyBlockingQueue(int capacity){
        elems = new String[capacity];
    }

    //入队列方法;
    public void put(String elem) throws InterruptedException {
        synchronized (this){
            while (size == elems.length){
                this.wait();
            }
            elems[tail] = elem;
            tail++;
            if(tail >= elems.length){
                tail = 0;
            }
            size++;
            this.notifyAll();
        }
    }
    //出队列方法;
    public String take() throws InterruptedException {
        String elem = null;
        synchronized (this){
            while (size == 0){
                this.wait();
            }
            elem = elems[head];
            elems[head] = null;
            head++;
            if(head >= elems.length){
                head = 0;
            }
            size--;
            this.notify();
        }
        return elem;
    }
}

四、使用阻塞队列实现“生产者-消费者模型”

代码内容分析

有一个阻塞队列和两个线程,其中

线程 t1 做为“生产者”,不断“生产”自增的数字 num ,并将数字入队列。

线程 t2 做为“消费者”,不断“消费”队列中的数字 num ,即将 num 从队列中取出并打印。

使用上述自己实现的阻塞队列,代码演示“生产者-消费者模型”:

    public static void main(String[] args) throws InterruptedException {
        //新建容量为10的阻塞队列;
        MyBlockingQueue queue = new MyBlockingQueue(10);

        //生产者:不断生产自增的num;
        Thread t1 = new Thread(()->{
            int num = 0;
            while (true){
                try {
                    queue.put(num + "");
                    System.out.println("生产者:" + num);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                num++;
            }
        });

        //消费者:每隔1秒消费一个num;
        Thread t2 = new Thread(()->{
            while (true){
                try {
                    System.out.println("消费者:" + queue.take());
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        t1.start();
        t2.start();
    }

//运行结果:
生产者:0
消费者:0
生产者:1
生产者:2
生产者:3
生产者:4
生产者:5
生产者:6
生产者:7
生产者:8
生产者:9
生产者:10
消费者:1
生产者:11
消费者:2
生产者:12
消费者:3
生产者:13
...

可以看到在生产者将阻塞队列放满后,开始阻塞,
等待消费者取出元素后,才又开始生产元素。

阅读指针 -> 《经典设计模式之“定时器”》

<JavaEE> 经典设计模式之 -- 定时器-CSDN博客介绍什么是定时器,以及 Java 标准库中的定时器类。实现自己的定时器类。https://blog.csdn.net/zzy734437202/article/details/134837039

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

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

相关文章

10-tornado项目部署

1. python3的安装和配置 1.1 安装系统依赖包 sudo dnf install wget yum-utils make gcc openssl-devel bzip2-devel libffi-devel zlib-devel -y1.2 下载Python wget https://www.python.org/ftp/python/3.9.5/Python-3.9.5.tgz1.3 解压 tar xzf Python-3.9.5.tgz 1.4 安装…

限流算法,基于go的gRPC 实现的

目录 一、单机限流 1、令牌桶算法 3、固定窗口限流算法 4、滑动窗口 二、集群限流 1、分布式固定窗口 &#xff08;基于redis&#xff09; 2、分布式滑动窗口 一、单机限流 1、令牌桶算法 令牌桶算法是当流量进入系统前需要获取令牌&#xff0c;没有令牌那么就要进行限…

php操作数据库,用wampserver工具

php操作数据库&#xff0c;用wampserver工具 打开wampserver数据库可视化&#xff0c;创建表格&#xff0c;插入数据 DROP TABLE IF EXISTS user; CREATE TABLE IF NOT EXISTS user (user_Id int NOT NULL AUTO_INCREMENT COMMENT 用户编号,user_Name varchar(20) CHARACTER S…

MySQL生成UUID并去除-

uuid()函数 uuid() 函数可以使mysql生成uuid,但是uuid中存在-,如下图&#xff1a; 去除uuid的- 默认生成的uuid含有-&#xff0c;我们可以使用replace函数替换掉-&#xff0c;SQL如下 select replace(uuid(),"-","") as uuid;Insert语句中使用UUID 如果…

VR远程带看,助力线下门店线上化转型“自救”

VR远程带看&#xff0c;因自身高效的沉浸式在线沟通功能&#xff0c;逐渐走进了大众的视野。身临其境的线上漫游体验以及实时同屏互联的新型交互模式&#xff0c;提升了商家同用户之间的沟通效率&#xff0c;进一步实现了远程线上一对一、一对多的同屏带看&#xff0c;用户足不…

解决Error:You‘re using an RSA key with SHA-1, which is no longer allowed

一、问题 在微信开发者工具中&#xff0c;推送代码时发生错误Error:You‘re using an RSA key with SHA-1, which is no longer allowed...... 奇怪的是命令行可以正常push: 原因&#xff1a;因为生成密钥的RSA算法&#xff0c;由于安全性原因&#xff0c;现在已经不允许使用…

Elasticsearch:什么是机器学习?

机器学习定义 机器学习 (ML) 是人工智能 (AI) 的一个分支&#xff0c;专注于使用数据和算法来模仿人类的学习方式&#xff0c;并随着时间的推移逐渐提高准确性。 计算机科学家和人工智能创新者 Arthur Samuel 在 20 世纪 50 年代首次将其定义为 “赋予计算机无需明确编程即可学…

【每日一题】—— C. Removal of Unattractive Pairs(Codeforces Round 913 (Div. 3))(思维)

&#x1f30f;博客主页&#xff1a;PH_modest的博客主页 &#x1f6a9;当前专栏&#xff1a;每日一题 &#x1f48c;其他专栏&#xff1a; &#x1f534; 每日反刍 &#x1f7e1; C跬步积累 &#x1f7e2; C语言跬步积累 &#x1f308;座右铭&#xff1a;广积粮&#xff0c;缓称…

【FPGA图像处理实战】- 图像处理前景如何?就业前景如何?

图像处理是FPGA应用的主要领域之一&#xff0c;图像处理数据量特别大且对实时性处理要求高的场景&#xff0c;这恰好能发挥FPGA流水线可实时处理的优势。 那么FPGA图像处理的前景如何&#xff1f; 一、FPGA开发&#xff08;图像处理&#xff09;招聘就业情况 看FPGA图像处理…

Notes数据直接在Excel中统计

大家好&#xff0c;才是真的好。 我希望你看过前面两篇内容《Domino REST API安装和运行》和《Domino REST API安装和运行》&#xff0c;因为今天我们正是使用REST API方式在Excel中查询和统计Notes数据。 不过首先你得知道一个OData协议&#xff0c;全名Open Data Protocol(…

智能优化算法应用:基于蜜獾算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于蜜獾算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于蜜獾算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.蜜獾算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…

Unity中Batching优化的静态合批

文章目录 前言一、静态合批的规则1、模型使用同一个材质2、勾选静态合批3、对于静态合批后的Mesh顶点总数&#xff0c;不超过2^16^即可以使用同一批次&#xff0c;超过则会开启一个新的批次4、对与使用同一材质的不同模型间&#xff0c;纹理贴图的问题&#xff0c;我们可以通过…

基于高德API实现网络geoJSON功能(突出省份)

代码实现&#xff1a; <script>// 3、初始化一个高德图层const gaode new ol.layer.Tile({title: "高德地图",source: new ol.source.XYZ({url: http://wprd0{1-4}.is.autonavi.com/appmaptile?langzh_cn&size1&style7&x{x}&y{y}&z{z},w…

git 面试字节时,老师问:合并分支中 rebase 和 merge 的区别

实际开发工作的时候&#xff0c;我们都是在自己的分支开发&#xff0c;然后将自己的分合并到主分支&#xff0c;那合并分支用2种操作&#xff0c;这2种操作有什么区别呢&#xff1f; git上新建一个项目&#xff0c;默认是有master分支的&#xff0c;将项目克隆到本地&#xff…

C/C++,数值计算——快速幂(Quick Pow)的几种算法源代码

1 文本格式 // C Version long long binpow(long long a, long long b) { if (b 0) return 1; long long res binpow(a, b / 2); if (b % 2) return res * res * a; else return res * res; } // C Version long long binpow(long long a,…

Nginx的安装、升级和管理

目录 一. nginx介绍 1. nginx简介 2. nginx和apache区别 二. nginx编译安装 1. 下载解压nginx安装包&#xff0c;并安装nginx依赖包 2. 创建运行用户和组 3. 编译安装并补全 4. 效验结果 三. 平滑升级nginx 1. 下载解压nginx安装包 2. 编译安装 3. 替换二进制文件 …

uniapp使用v-html调用接口,富文本图片 视频自适应大小

前端获取到后台数据 不做处理 就会出现下面问题 图片 视频超出视图显示不全 处理 //info 是富文本 <view v-ifinfo v-htmlreplaceWhite(info)></view>调用下面方法 replaceWhite(html) { // 处理富文本默认图片&#xff0c;视频大小let newContent html.replace…

EPICS modbus 模块数字量读写练习

本文使用modbus slave软件模拟一个受控的modbus设备&#xff0c;此模拟设备提供如下功能&#xff1a; 1、线圈1&#xff0c;起始地址为0&#xff0c;数量为8&#xff0c;软件设置如下(功能码1)&#xff1a; 2、线圈2&#xff0c;起始地址为8&#xff0c;数量为8&#xff0c;软…

easyui实现省市县三级联动

一、技术: 前端采用的是easyui+jquery+jsp页面 后端采用springmvc+mybatis+mysql8 效果图 二、cascadeEasyui.jsp页面 <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%> <%String path = request.getContex…

Stable Diffusion 系列教程 - 2 WebUI 参数详解

Stable Diffusion 的整个算法组合为&#xff1a; UNet VAE 文本编码器 UNet&#xff1a;就是我们大模型里的核心。 文本编码器&#xff1a;将我们的prompt进行encoder为算法能理解的内容&#xff08;可以理解为SD外包出去的项目CLIP&#xff09;。 VAE&#xff1a;对UNet生…