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

目录

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

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

三、实现自己的阻塞队列

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

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

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

相关文章

react新旧生命周期钩子

以下的内容根据尚硅谷整理。 旧生命钩子 辅助理解&#xff1a; 红色框&#xff1a;挂载时生命钩子蓝色框&#xff1a;更新时生命钩子绿色框&#xff1a;卸载时生命钩子 挂载时 如图所示&#xff0c;我们可以看到&#xff0c;在组件第一次挂载时会经历&#xff1a; 构造器&a…

数据库原理: 笛卡儿积

笛卡儿积&#xff08;Cartesian Product&#xff09;是集合论中的一个概念&#xff0c;也在数据库中的查询操作中经常使用。笛卡儿积是指两个集合&#xff08;或更多集合&#xff09;之间所有可能的组合。如果有两个集合A和B&#xff0c;它们的笛卡儿积记作A B&#xff0c;表示…

深入理解HashMap:Java中的键值对存储利器

HashMap是Java中常用的数据结构之一&#xff0c;它提供了一种键值对的存储机制&#xff0c;适用于快速查找和检索。本文将深入探讨HashMap的概念、内部结构、工作原理以及在多线程环境下的一些问题。 1. HashMap的概念 HashMap是Java中的一种数据结构&#xff0c;用于存储键值…

RPC简介和grpc的使用

文章目录 Rpc基本概念RPC 机制和实现过程RPC的机制的诞生和基础概念总结下RPC执行步骤&#xff1a; 安装gRPC和Protobuf安装proto 服务定义gRPC 优势 gRPC入门简单使用 代码仓库 Rpc基本概念 RPC&#xff08;Remote Procedure Call&#xff09;远程过程调用&#xff0c;是一种…

学习IO的第四天

作业 : 使用两个子进程完成两个文件的拷贝&#xff0c;子进程1拷贝前一半内容&#xff0c;子进程2拷贝后一般内容&#xff0c;父进程用于回收两个子进程的资源 #include <head.h>int main(int argc, const char *argv[]) {int rd -1;if((rdopen("./01_test.c&quo…

《点云进阶》专栏文章目录

目录 一、PCL进阶篇* 二、Open3D进阶篇 一、PCL进阶篇 * PCL 最小二乘拟合二维直线PCL 最小二乘拟合空间直线PCL 计算点云的倒角距离&#xff08;Chamfer Distance&#xff09;PCL 点云配准精度评价——点到面的均方根误差PCL 可视化八叉树PCL 计算Hausdorff距离PCL 从变换矩…

生物动力葡萄酒的快速指南

虽然我们大多数人都熟悉有机酿酒和农业&#xff0c;但围绕生物动力学仍有许多困惑和神秘。无论你是否完全陌生&#xff0c;或者你已经听到一些小道消息&#xff0c;我们在这里揭开这种独特的葡萄酒生产方法的神秘面纱。 生物动力葡萄酒就是一个更全面的有机酿酒过程&#xff0c…

微服务的利与弊

一、前言 自从大多数web架构从单体演进到服务拆分&#xff0c;到微服务一统天下的几年来&#xff0c;应该没有web应用不是微服务架构的吧。最开始是阿里的doubble分层架构&#xff0c;到后来的SpringCloud全家桶&#xff0c;还有各个大厂自己定义的一套服务治理框架。微服务无…

万界星空科技五金家具行业MES解决方案

MES系统如何与家具企业生产相匹配&#xff1f;相较于其它大多数工业软件&#xff0c;MES系统无疑是受企业欢迎的软件之一。MES系统处于制造生产企业信息化的核心领域&#xff0c;有着承上启下的作用。那MES系统如何与家具企业生产相匹配&#xff1f; 五金家具行业的工艺特点&am…

【C语言基础】嵌入式面试经典题(C语言篇)----有新的内容会及时补充、更新!

&#x1f4e2;&#xff1a;如果你也对机器人、人工智能感兴趣&#xff0c;看来我们志同道合✨ &#x1f4e2;&#xff1a;不妨浏览一下我的博客主页【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d;…

金融行业文件摆渡,如何兼顾安全和效率?

金融行业是数据密集型产业&#xff0c;每时每刻都会产生海量的数据&#xff0c;业务开展时&#xff0c;数据在金融机构内部和内外部快速流转&#xff0c;进入生产的各个环节。 为了保障基础的数据安全和网络安全&#xff0c;金融机构采用网络隔离的方式来隔绝外部网络的有害攻击…

【洛谷算法题】P1909-买铅笔【入门2分支结构】

&#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【洛谷算法题】 文章目录 【洛谷算法题】P1909-买铅笔【入门2分支结构】&#x1f30f;题目背景&#x1f30f;题目描述&…

c# OpenCV安装(一)

一 通过NuGet 安装四个拓展包 OpenCvSharp4、OpenCvSharp4.Extensions、OpenCvSharp4.runtime.win、OpenCvSharp4.WpfExtensions C#使用OpenCV的一些代码 需要加头文件 using OpenCvSharp; //为了使用opencv using Point OpenCvSharp.Point; //为了确定我们使用的poin…

智能井盖传感器产品介绍,井盖传感器推荐

智能井盖传感器是一种先进的设备&#xff0c;能够提高城市管理的智能化水平。该传感器作为城市生命线建设的核心组成部分&#xff0c;为智慧城市的正常建设提供了有力的保障&#xff0c;能够提高城市管理的智能化水平。这种设备通过高度灵敏的传感器网络&#xff0c;实时监测井…

pdi-ce-9.4.0.0-343.zip和pentaho-server-ce-9.4.0.0-343.zip区别及简单使用

目录 &#x1f351;一、概述&#x1f34a;1.1、pdi-ce-9.4.0.0-343.zip&#x1f34a;1.2、pentaho-server-ce-9.4.0.0-343.zip &#x1f351;二、简单使用&#x1f34a;2.1、pdi-ce-9.4.0.0-343&#x1f34a;2.2、pentaho-server-ce-9.4.0.0-343&#x1f34a;2.3、联合使用 &am…

Maven 概念模型

Maven 概念模型 Maven 包含了一个项目对象模型 (Project Object Model)&#xff0c;一组标准集合&#xff0c;一个项目生命周期(Project Lifecycle)&#xff0c;一个依赖管理系统(Dependency Management System)&#xff0c;和用来运行定义在生命周期阶段(phase)中插件(plugin)…

Docker实战笔记 二 Springboot Idea 插件打包

1.上传springboot的jar rootcenots-7.5:/home/code#rz -----app.jar 2.编辑Dockerfile rootcenots-7.5:/home/code#vi Dockerfile内容 FROM openjdk:8 # 作者 MAINTAINER nnd # 声明要使用的端口 EXPOSE 8080 # VOLUME 指定了临时文件目录为/tmp。# 将本地包添加到容器中并…

【网络奇缘】- 计算机网络|深入学习物理层|网络安全

​ &#x1f308;个人主页: Aileen_0v0&#x1f525;系列专栏: 一见倾心,再见倾城 --- 计算机网络~&#x1f4ab;个人格言:"没有罗马,那就自己创造罗马~" 回顾链接&#xff1a;http://t.csdnimg.cn/ZvPOS 这篇文章是关于深入学习原理参考模型-物理层的相关知识点&…

Spring boot 使用Redis 消息发布订阅

Spring boot 使用Redis 消息发布订阅 文章目录 Spring boot 使用Redis 消息发布订阅Redis 消息发布订阅Redis 发布订阅 命令 Spring boot 实现消息发布订阅发布消息消息监听主题订阅 Spring boot 监听 Key 过期事件消息监听主题订阅 最近在做请求风控的时候&#xff0c;在网上搜…

面试常问的dubbo的spi机制到底是什么?(上)

前言 dubbo是一款微服务开发框架&#xff0c;它提供了 RPC通信 与 微服务治理 两大关键能力。作为spring cloud alibaba体系中重要的一部分&#xff0c;随着spring cloud alibaba在国内活跃起来&#xff0c;dubbo也越来越深受各大公司的青睐。本文就来对dubbo的spi机制源码进行…