【多线程案例】阻塞队列,实现生产者消费者模型

news2024/9/23 9:25:04

阻塞队列(BlockingQueue

阻塞队列是多线程代码中比较常用的一种数据结构。是一种特殊的队列,带有阻塞特性。

为何说是一种特殊的队列?

1.线程安全

2.带有阻塞特性

  • 如果队列为空,继续出队列,就会发生阻塞。阻塞到其他线程往队列里添加元素为止。
  • 如果队列为满,继续入队列,就会发生阻塞。阻塞到其他线程从队列中取走元素为止。
  • 意义:可以用来实现"生产者消费者模型"。生产者消费者模型通俗的来讲,生产者负责生产东西,并将东西放到阻塞队列中,然后消费者就会从阻塞队列中获取内容,如果生产者生产的慢,消费者就得等待 (即消费者从空的队列中获取元素就得等待);相反如果生产者生产的快,生产者就可以休息速度慢下来 (即生产者从满的队列中添加元素就会阻塞等待)。

在java中标准库中针对zuseduilie提供两种实现方式:

基于数组:

BlockingQueue<String> queue = new ArrayBlockingQueue<>(5);

基于链表:

BlockingQueue<String> queue = new LinkedBlockingDeque<>();

方法:

put()      入队列 带有阻塞性质

take()    出队列 带有阻塞性质

实现基于数组(循环队列)的阻塞队列:

//实现阻塞队列
class myBlockingQueue{

    //锁对象
    private Object object = new Object();

    //队列采用循环队列  数组
    private String[] data = new String[1000];

    //队头元素位置 加volatile防止内存可见性问题
    private volatile int head = 0;
    //队尾元素位置
    private volatile int tail = 0;
    //有效长度
    private volatile int size = 0;

    //带有阻塞性质的入队操作put
    public void put(String str) throws InterruptedException {
        synchronized(object) {
            //队列满时
            if(size==data.length) {
                //阻塞等待 等待另一个线程调用notify方法唤醒
                object.wait();
            }
            //队列不满 入队列
            data[tail] = str;
            tail++;
            size++;
            object.notify();

            //由于数组循环使用 也防止索引出界
            if(tail==data.length) {
                tail = 0;
            }
        }

    }

    //带有阻塞性质的出队列操作
    public String take() throws InterruptedException {
        synchronized(object) {
            //队列为空
            if(size==0) {
                //阻塞等待
                object.wait();
            }
            //队列不为空
            String tmp = data[head];
            head++;
            if(head==data.length) {
                head = 0;
            }
            size--;
            //唤醒
            object.notify();
            return tmp;
        }

    }

}

代码实现中的一些细节:

  • 指向队头,队尾元素,size在代码中可能会出现内存可见性问题,要加volatile。
  • 入队,出队方法都存在着可能会影响线程安全的读,修改操作,最好给整个方法加锁。
  • 虽然在一个方法中有wait和notify,但是一个队列满队列和空队列不会同时出现。并且使用wait进行阻塞等待时,是由另一个线程中的notify唤醒的。
  • 抛异常可以是方法后跟throws,也可以是try...catch...,但这里应该使用throws,原因是try...catch...执行后程序不会停止,还是继续向下执行,对应代码就是入队操作判断队列满时,如果使用try...catch...,程序出现异常后,向下再接着执行,是会覆盖掉队列中其他未执行的内容的,而使用throws若程序出现异常,会抛出异常后整个方法就结束了,interrupt唤醒了wait。
  • 使用wait的时候,往往都是使用while作为条件判定的方式,java源码解释也是推荐while。目的就是为了让wait唤醒之后还能再确认一次,是否条件仍然满足。
  • 一个队列,空和满只能同时出现一种,take和put只有一边能阻塞。如果put阻塞了,其他线程继续调用put也都会阻塞,只有靠take唤醒,如果take阻塞了,其他线程继续调用take也都会阻塞,只能靠put唤醒。

实现生产者消费者模型

生产者 - 消费者模型( Producer-consumer problem) 是一个非常经典的多线程并发协作的模型,在分布式系统里非常常见。

这个模型由两类线程和一个缓冲区组成来组成

  • 生产者线程:生产数据,并把数据放在这个队列里面
  • 缓冲区:存放生产者的数据的地方即阻塞队列
  • 消费者线程:从队列里面取数据,消费数据

运行流程

  • 生产者和消费者在同一时间段内共用同一个存储空间
  • 生产者往存储空间中添加产品
  • 消费者从存储空间中取走产品
  • 当存储空间为空时,消费者阻塞,当存储空间满时,生产者阻塞。
     

实现"生产者消费者模型"好处:

(1)解耦合

        两个模块之间联系越紧密,耦合就越高。尤其对于分布式系统来说,十分有意义。

(2)削峰填谷

        峰:指短时间内请求多。

        比如服务器和客户端之间的请求与响应,当用户量请求增大时,服务器也会受牵连,甚至于            将服务器弄崩溃给挂了,耦合性较高,如果两者之间用一种数据结构如队列存储请求,就不论客户            端用户量请求有多大时,服务器仍然可以按照自己的速度去处理请求。

消息队列:当把阻塞队列封装成单独的服务器程序,部署到特定的机器上,这个时候就把这个队列称为"消息队列"。

实现生产者消费者模型代码:

//实现阻塞队列
class myBlockingQueue{

    //锁对象
    private Object object = new Object();

    //队列采用循环队列  数组
    private String[] data = new String[1000];

    //头指针 加volatile防止内存可见性问题
    private volatile int head = 0;
    //尾指针
    private volatile int tail = 0;
    //有效长度
    private volatile int size = 0;

    //带有阻塞性质的入队操作put
    public void put(String str) throws InterruptedException {
        synchronized(object) {
            //队列满时
            while (size==data.length) {
                //阻塞等待 等待另一个线程调用notify方法唤醒
                object.wait();
            }
            //队列不满 入队列
            data[tail] = str;
            tail++;
            size++;
            object.notify();

            //由于数组循环使用 也防止索引出界
            if(tail==data.length) {
                tail = 0;
            }
        }

    }

    //带有阻塞性质的出队列操作
    public String take() throws InterruptedException {
        synchronized(object) {
            //队列为空
            while (size==0) {
                //阻塞等待
                object.wait();
            }
            //队列不为空
            String tmp = data[head];
            head++;
            if(head==data.length) {
                head = 0;
            }
            size--;
            //唤醒
            object.notify();
            return tmp;
        }

    }
}



//借助阻塞队列 实现生产者消费者模型
public class test {
    public static void main(String[] args) {
        MyBlockingQueue queue = new MyBlockingQueue();

        //生产者模型
        Thread t1 = new Thread(()->{
            int num = 1;
            while(true) {
                try {
                    queue.put(num);
                    System.out.println("生产者生产"+num);
                    num++;
                    //Thread.sleep(1000);     //生产者有节奏生产
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        //消费者模型
        Thread t2  =new Thread(()->{
            while(true) {
                try {
                    int tmp = queue.take();
                    System.out.println("消费者消费"+tmp);
                    Thread.sleep(1000);     //消费者有节奏消费
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        t2.start();
    }
}

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

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

相关文章

零基础Linux_13(基础IO_文件)文件系统接口+文件描述符fd+dup2函数

目录 1. C语言的文件操作 1.1 C语言文件的写入 1.2 当前路径 1.3 文件操作模式 1.4 文件的读取和cat 2. 文件系统接口 2.1 系统调用与封装 2.2 open打开文件 2.2.1 flags标记位 2.2.2 open用法演示 2.3 close关闭文件和write写入文件和rede读取文件 2.3.1 O_TRUNC…

基于JSP的动漫论坛

摘 要 作为文化产业的一部分&#xff0c;动漫影响了我国一代又一代青少年&#xff0c;据钱江晚报调查显示&#xff0c;有超过七成的95后愿意从事与动漫相关的行业&#xff0c;可见其对青少年影响力之大。 动漫论坛作为最先开始热爱动漫人士进行交流的方式之一&#xff0c;是爱…

Reactor网络模式

文章目录 1. 关于Reactor模式的了解2. 基于Reactor模式实现epoll ET服务器2.1 EventItem类的实现2.2 Reactor类的实现Dispatcher函数AddEvent函数DelEvent函数EnableReadWrite函数 2.3 四个回调函数的实现acceptor回调函数recver回调函数sender回调函数errorer回调函数 3. epol…

TensorFlow入门(十五、数据读取机制(2))

使用Dataset创建和读取数据集,作为TensorFlow模型创建输入管道的新方式,使用性能比使用feed_dict或队列式管道的性能高很多,使用也更加简洁容易。也是google强烈推荐的数据读取方式,对于TensorFlow而言,十分重要。 Dataset是什么? Dataset的定义 : 它是一个含有相同类型元素且…

C++局部变量 成员变量 全局变量(及文件内外全局变量)

在C中&#xff0c;类的局部变量、成员变量、全局变量、静态全局变量&#xff1a; 局部变量&#xff1a;局部变量是在函数内部定义的变量&#xff0c;它只能在该函数的范围内被访问和修改。当函数执行结束后&#xff0c;局部变量的内存空间会被释放。局部变量主体是函数。局部变…

如何成为一名云计算构架师,看这里!

都说&#xff0c;每个程序员心中都有一个成为架构师的梦想&#xff01; 因为不管是对于自身专业技能的认可&#xff0c;还是立足于现实的薪资&#xff0c;都是令人向往的&#xff01; 1.云计算架构师介绍 云计算架构师负责管理企业中的云计算架构&#xff0c;尤其是在云技术…

P1867 【Mc生存】经验值

#include <stdio.h>int main(void) {int n; //n项操作double HP 10; //总生命值&#xff0c;初始化为10&#xff0c;上限是10int LV 0; //等级&#xff0c;初始化为1int EXP 0; //总经验值&#xff0c;初始化为0double de_HP; //减少的生命值int in_EXP; //增加…

react学习(三——实战项目)

创建 npm init vite小知识 "scripts": {"dev": "vite --host --port 3002 --open", //--host会在终端显示IP&#xff0c;--port 3002把显示端口改为3002&#xff0c;--open会在启动后打开链接"build": "tsc && vite bui…

如何自学(黑客)网络安全技术————(详细分析学习思路)

前言 前几天发布了一篇 网络安全&#xff08;黑客&#xff09;自学 没想到收到了许多人的私信想要学习网安黑客技术&#xff01;却不知道从哪里开始学起&#xff01;怎么学&#xff1f;如何学&#xff1f; 今天给大家分享一下&#xff0c;很多人上来就说想学习黑客&#xff0…

IIC总线

IIC总线原理 时序图作业 MPU6050 MPU6050是一个运动处理传感器&#xff0c;其内部集成了3轴加速度传感器 和3轴陀螺仪&#xff08;角速度传感器&#xff09;,以及一个可扩展数字运动处理器

php伪协议 [ACTF2020 新生赛]Include1

打开题目 点击后 期间bp抓包也一无所获 那我们回到题目上来 我们可知这是文件包含漏洞&#xff0c;但是我们直接读取的是flag.php文件&#xff0c;而非flag.php文件源码&#xff0c;那我们想要获取文件源码&#xff0c;这里就涉及到php伪协议 php://filter可以获取指定文件源码…

typename、typedef、using对比

在c的标准库中&#xff0c;因为类继承关系比较复杂和模板使用比较多的原因&#xff0c;源代码中充斥着typename、typedef和using这三个关键字 一、typename关键字 1.1、typename的第一个作用是用作模板里面&#xff0c;来声明某种类型 template<typename _Tp, typename _…

根据二叉树创建字符串--力扣

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;强烈推荐优质专栏: &#x1f354;&#x1f35f;&#x1f32f;C的世界(持续更新中) &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;…

stm32 hal库 st7789 1.54寸lcd

文章目录 前言一、软件spi1.cubemx配置2.源码文件 二、硬件spi1.cubemx配置2.源码文件3.小小修改 总结 前言 1.54寸lcd 240*240 一、软件spi 1.cubemx配置 一定要注意把这几个东西上拉。 使用c8 2.源码文件 我使用的是中景园的源码&#xff0c;他本来是是标准库的稍微修改…

死灰复燃!QakBot 恶意软件仍在运行中

2023 年 8 月&#xff0c;美国联邦调查局宣布&#xff0c;在名为“猎鸭行动”的国际执法活动中&#xff0c;成功拆除 Qakbot 僵尸网络&#xff08;Qakbot 也称 QBot、QuackBot 和 Pinkslipbot&#xff0c;自 2008 年以来一直非常活跃&#xff09;。然而 Security A ffairs 网站…

好用的便签软件如何实现定时提醒?支持设定定时提醒的便签

不论你是职场白领&#xff1f;还是销售精英&#xff1f;亦或者是家庭主妇&#xff0c;每天都会面临着大量的事务需要处理&#xff0c;怎么才能高效规划管理自己每日事务&#xff0c;以及进行时间管理成为一项比较重大的挑战&#xff0c;便签类软件可以辅助大家按时完成各项任务…

Django REST framework API版本管理【通过GET参数传递】

API版本 在开发过程中可能会有多版本的API&#xff0c;因此需要对API进行管理。django drf中对于版本的管理也很方便。 http://www.example.com/api/v1/info http://www.example.com/api/v2/info 上面这种形式就是很常见的版本管理 在restful规范中&#xff0c;后端的API需…

基于安卓android微信小程序宠物交易小程序

运行环境 开发语言&#xff1a;Java 框架&#xff1a;ssm JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 小程序框架&…

七、全屏粒子特效

简介 给页面添加粒子光影特效。欢迎访问个人的简历网站预览效果 本章涉及修改与新增的文件&#xff1a;main.ts、App.vue、utils 一、安装插件 安装 vue3-particles tsparticles插件 详细文档查看 tsParticles 官网 npm i vue3-particlesnpm i tsparticles创建配置文件 o…

封装axios的post请求踩坑记录

今天自己封装axios请求时遇到了两个坑&#xff0c;所以记录一下。 第一个坑是我在代码的逻辑是只会发一次请求&#xff0c;但是在控制台却发现发了两次&#xff0c;第一次为不带参数的请求方式为options的预检请求&#xff0c;第二次的请求才是我真正需要发的post请求。在经过上…