news 2026/4/3 7:58:22

设计模式之一——堵塞队列

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
设计模式之一——堵塞队列

堵塞队列呢是一种更为复杂的队列,他对比简单的队列有两个特性:1)线程安全;2)堵塞:a)队列为空时,尝试出队列,出队列操作就会堵塞,直到有新的元素添加进来为止;b)队列满了时,尝试入队列操作时会发生堵塞,直至队列被取走为止。

生产者消费者模型

堵塞队列呢有一个非常主要的场景:实现“生产者消费者模型”。什么是生产者消费者模型呢?

就拿这个服务器举例:这里直接A服务器请求B服务器,B服务器响应A服务器这种就不是生产者消费者模型。

当我们加入一个中转栈的时候就是实现了生产者消费者模型了,当A服务器想要去请求B服务器时,A服务器直接在中间栈中取即可,B服务器的响应也是直接发送给中间栈即可,这个中间栈呢也是我们今天要学的——堵塞队列

生产者消费者模型的优缺点

1.解耦合

通过上述服务器与服务器之间呢我们可以发现,当我们需要更改服务器A/B时,服务器B/A也会受到影响,这时这两个服务器就是高耦合了,但如果加入堵塞队列,我们更改某个服务器的代码时就不需要管另一个服务器的业务了,他们之间通过了堵塞队列来连接,而堵塞队列里的逻辑也不会有这两个服务器业务复杂。

2.削峰填谷

我们上过大学都知道,每当抢课时学校的官网都会崩溃,这就是因为进入官网的人太多了,所以A服务器一般都会有某个时刻点进去的人多,但B服务器的响应又有限,所以当我们直接连接两个服务器时可能造成服务器崩溃。加入了堵塞队列后,当点击A服务器的人多了就会对堵塞队列请求的多,但B服务器还是以之前的速度响应,不会影响到B服务器,但趁着这个峰值过去了服务器B还是以之前的响应速度响应传给堵塞队列,故而等下次点击量波峰时可以有效的缓解。

3.缺点

1.使代码变得复杂

引入队列之后,整体的结果会变得更复杂,此时,就需要更多的机器来部署,生产环境的结果复杂,管理起来也复杂。

2.效率降低

不使用堵塞队列时,服务器与服务器之间的请求和响应都是直接的,引入了堵塞队列他们还得加载到队列中,当数量达到一定量时会影响效率。

堵塞队列的使用

BlockingQueue的介绍

堵塞队列的类是BlockingQueue:

通过观察我们发现,new时会给我们很多对象,这里我们就讨论三个:ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue;

ArrayBlockingQueue:这时一个基于数组实现的堵塞队列;

LinkedBlockingQueue:这是一个基于链表实现的堵塞队列;

PriorityBlockingQueue:这时基于优先级队列实现的堵塞队列;

BlockingQueue这个类呢我们点击去发现他是继承了Queue类的,使用当我们使用这个类时是可以当作普通类来使用的;但我们学BlockingQueue的目的是学习堵塞队列,故而我们就讲讲堵塞队列的使用。

BlockingQueue的使用

这样子我们就简单的创建了一个容量为100的堵塞队列。

当我们使用put方法时就是往堵塞队列中加入元素了,当队列满了在添加时就会堵塞等待。

弹出队列是take(),当队列为空时就会堵塞等待。

堵塞队列的使用就讲到这里,我们重点讲解堵塞队列的实现!!!!

堵塞队列的实现

堵塞队列的实现主要涉及到一下几个问题:

1)怎么插入元素

2)怎么取出元素

3)怎么实现堵塞

1.插入与删除元素

我们实现堵塞队列主要以数组的方式来实现,插入和删除元素可以定义一个头指针和尾指针,插入与删除就是头指针和尾指针加减。

2.怎么实现堵塞

我们之前学过wait和notify,这两个就可以很好的实现堵塞等待,当数组满了的时候就使用wait等待,等到有元素的删除即可发出notify来截至堵塞。空队列也是如此。

代码的实现

class MyBlockingQueue{ private String[] date = null; private int head = 0;//头指针 private int tail = 0;//尾指针 private int size = 0;//元素个数 public MyBlockingQueue(int capacity) { date = new String[capacity]; } public void put(String elem){ if(size >= date.length){ //堵塞等待 } date[tail] = elem; tail++; if(tail >= date.length){ tail = 0; //因为队列是先进先出的,当tail大于或等于了数组长度时, // 说明数组添加元素从头开始了,这里也不要害怕head没有走,因为有堵塞等待。 } } public String take(){ if(size == 0){ //队列为堵塞等待 } String ret = date[head]; head++; if(head >= date.length){ head =0; //因为队列是先进先出的,当队列走到了尽头的时候数组, // 因为put是从头开始来的,所以令head为0, // 当然也不要担心head和tail撞见,因为有堵塞等待 } size--; return ret; } }

第一步,我们设计出了基本的框架,这时插入和删除已经完成,但还差堵塞等待:我们得考虑wtai和notify在哪里加入?

class MyBlockingQueue{ private String[] date = null; private int head = 0;//头指针 private int tail = 0;//尾指针 private int size = 0;//元素个数 public MyBlockingQueue(int capacity) { date = new String[capacity]; } public void put(String elem) throws InterruptedException { synchronized (this){ if(size >= date.length){ this.wait(); //堵塞等待 } date[tail] = elem; tail++; if(tail >= date.length){ tail = 0; //因为队列是先进先出的,当tail大于或等于了数组长度时, // 说明数组添加元素从头开始了,这里也不要害怕head没有走,因为有堵塞等待。 } size++; this.notify(); } } public String take() throws InterruptedException { synchronized (this){ if(size == 0){ this.wait(); //队列为堵塞等待 } String ret = date[head]; head++; if(head >= date.length){ head =0; //因为队列是先进先出的,当队列走到了尽头的时候数组, // 因为put是从头开始来的,所以令head为0, // 当然也不要担心head和tail撞见,因为有堵塞等待 } size--; this.notify(); return ret; } } }

wait和notify呢可以加的也很简单,wait可以直接在 if(size == 0)和 if(size >= date.length)里边加,因为这里就是为了他们堵塞等待的,notify呢可以加载最后面,因为此时也快要解锁了让这两个方法能更好的竞争锁。

但这里还面临这一个问题,我们知道堵塞是可以通过其他方法唤醒的,比如Interrupt,当wait被其他方法唤醒时会出现队列满了/空了也会执行下去造成越界,而出现bug,所以我们可以把两个if改成while循环,此时即使wait提前被唤醒了也还会经过while判断是否成立,成立即退出,不成立则继续循环,故而最终代码如下:

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) { // 队列满了. 需要阻塞的 // return; this.wait(); } data[tail] = elem; tail++; if (tail >= data.length) { tail = 0; } // tail = (tail + 1) % data.length; size++; this.notify(); } } public String take() throws InterruptedException { synchronized (this) { while (size == 0) { // 队列空了. 需要阻塞 // return null; this.wait(); } String ret = data[head]; head++; if (head >= data.length) { head = 0; } size--; this.notify(); return ret; } } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/3 6:21:42

W5500在工业自动化中的应用:实战案例解析

以下是对您提供的博文《W5500在工业自动化中的应用:实战案例解析》进行 深度润色与专业重构后的终稿 。全文已严格遵循您的全部优化要求: ✅ 彻底去除AI痕迹,语言自然、老练、有工程师温度; ✅ 所有标题重写为逻辑驱动型小节名,无“引言/概述/总结”等模板化结构; ✅…

作者头像 李华
网站建设 2026/3/26 15:53:10

解锁GTA V无限可能:ScriptHookV深度探索与创意实践

解锁GTA V无限可能:ScriptHookV深度探索与创意实践 【免费下载链接】ScriptHookV An open source hook into GTAV for loading offline mods 项目地址: https://gitcode.com/gh_mirrors/sc/ScriptHookV 你是否曾幻想过改变GTA V的游戏规则?想在洛…

作者头像 李华
网站建设 2026/4/3 4:12:02

解锁GTA V无限可能:ScriptHookV模组开发探索指南

解锁GTA V无限可能:ScriptHookV模组开发探索指南 【免费下载链接】ScriptHookV An open source hook into GTAV for loading offline mods 项目地址: https://gitcode.com/gh_mirrors/sc/ScriptHookV 你是否曾幻想过在洛圣都的街头驾驶从未见过的概念跑车&am…

作者头像 李华
网站建设 2026/3/21 7:06:11

cv_unet_image-matting支持中文界面吗?语言配置修改方法说明

cv_unet_image-matting支持中文界面吗?语言配置修改方法说明 1. 关于cv_unet_image-matting WebUI的中文支持现状 cv_unet_image-matting图像抠图WebUI由科哥二次开发构建,基于U-Net架构实现高质量人像抠图。目前该WebUI默认采用英文界面,但…

作者头像 李华
网站建设 2026/4/1 23:51:09

NewBie-image-Exp0.1部署教程:test.py脚本修改与图片生成步骤

NewBie-image-Exp0.1部署教程:test.py脚本修改与图片生成步骤 1. 为什么选NewBie-image-Exp0.1?小白也能上手的动漫生成利器 你是不是也试过下载一个AI绘图项目,结果卡在环境配置上一整天?装完CUDA又报错PyTorch版本不匹配&…

作者头像 李华
网站建设 2026/3/28 7:18:43

零成本变身:让闲置Joy-Con手柄成为PC游戏利器的环保方案

零成本变身:让闲置Joy-Con手柄成为PC游戏利器的环保方案 【免费下载链接】XJoy 项目地址: https://gitcode.com/gh_mirrors/xjo/XJoy 你是否也曾为角落里积灰的任天堂Joy-Con手柄感到惋惜?这些曾经带来无数欢乐的游戏配件,往往在购买…

作者头像 李华