news 2026/5/20 16:21:21

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

作者头像

张小明

前端开发工程师

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/5/19 18:52:35

CSV转OFX高效转换指南:普通用户的财务数据标准化教程

CSV转OFX高效转换指南:普通用户的财务数据标准化教程 【免费下载链接】HoYo.Gacha ✨ An unofficial tool for managing and analyzing your miHoYo gacha records. (Genshin Impact | Honkai: Star Rail) 一个非官方的工具,用于管理和分析你的 miHoYo 抽…

作者头像 李华
网站建设 2026/5/13 13:52:12

Qwen3-Embedding-0.6B部署踩坑总结,少走弯路

Qwen3-Embedding-0.6B部署踩坑总结,少走弯路 你是不是也经历过:兴冲冲下载了Qwen3-Embedding-0.6B,照着文档敲完命令,结果卡在启动失败、API调不通、向量维度对不上、中文乱码、显存爆掉……最后对着报错日志发呆一小时&#xff…

作者头像 李华
网站建设 2026/5/20 5:28:16

如何用ScriptHookV从零开始定制GTA V游戏体验:零基础完全指南

如何用ScriptHookV从零开始定制GTA V游戏体验:零基础完全指南 【免费下载链接】ScriptHookV An open source hook into GTAV for loading offline mods 项目地址: https://gitcode.com/gh_mirrors/sc/ScriptHookV 你是否想让GTA V变得更有趣?Scri…

作者头像 李华
网站建设 2026/5/15 1:09:57

Qwen 1.5B vs Llama3推理对比:数学与代码生成实战评测

Qwen 1.5B vs Llama3推理对比:数学与代码生成实战评测 1. 为什么这场对比值得你花5分钟看完 你有没有遇到过这样的情况: 想快速验证一个数学思路,却要翻半天公式手册; 写一段Python脚本处理数据,卡在边界条件上反复调…

作者头像 李华
网站建设 2026/5/17 8:38:06

Silk-V3-Decoder:音频格式转换完全指南

Silk-V3-Decoder:音频格式转换完全指南 【免费下载链接】silk-v3-decoder [Skype Silk Codec SDK]Decode silk v3 audio files (like wechat amr, aud files, qq slk files) and convert to other format (like mp3). Batch conversion support. 项目地址: https:…

作者头像 李华
网站建设 2026/5/16 20:06:12

8步生成照片级图像!Z-Image-Turbo到底有多快?

8步生成照片级图像!Z-Image-Turbo到底有多快? 你有没有试过等一张AI图等得去泡了杯咖啡、回了三封邮件,结果页面还在转圈? 这次不一样了。 输入一句话,8步采样,1秒内——不是“大概一秒”,是实…

作者头像 李华