JavaEE 第9节 阻塞队列详解

embedded/2024/10/21 3:33:21/

一、概念

阻塞队列是在普通队列(先进先出的数据结构)的基础上增加了阻塞属性的特殊队列

       1)当阻塞队列空的时候,如果继续出队元素会进入阻塞状态,直到其他线程入队元素。

       2)当阻塞队列满的时候,如果继续入队元素也会进入阻塞状态,直到其他线程出元素。

阻塞队列队列的一个典型应用场景就是“生产者消费者模型”。


二、生产者消费者模型

生产者消费者模型共有三大模块:

1、生产者 

2、缓冲区(一般指的就是阻塞队列)

3、消费者

这里面生产者产生的数据需要传递给消费者去处理,但是生产者与消费者之间不是直接通信的而是借助中间模块“缓冲区”,也就是阻塞队列来完成

生产者把产生的数据都抛给阻塞队列,消费者获取数据也全在阻塞队列里面找。

举一个包饺子的形象例子:

如果左孩子,擀饺子皮速度比右孩子包饺子速度快,那么盖帘(阻塞队列)很可能就满了,满了的话,左孩子就会停下来玩会儿手机(进入阻塞状态),等右孩子(消费者)从盖帘(阻塞队列)拿走一个饺子皮,然后左孩子在取擀饺子皮。

阻塞队列优点

1、平衡生产者和消费者的处理能力,保护下游服务器

在上述包饺子的例子中,当生产者产生的产生的数据比较多的时候(擀面擀的包饺子的人快很多),如果没有阻塞队列,数据直接全部都给到消费者那么消费者处理数据的压力就非常大,甚至是服务器崩溃。但是有了阻塞队列作为缓冲区,就可以避免这种事情的发生。

有没有这样的疑问,数据量的激增是从生产者开始,然后是阻塞队列,最后是消费者的,为什么生产者、阻塞的队列不会先挂?


这实际上是与生产者和消费者的处理数据的规模量有关。

生产者实际上就是一个请求接收和转移窗口,它对数据的处理其实非常少(阻塞队列同理),相当于就是负责传输数据的,但是消费者不仅要接收数据,还有对数据进行计算处理,处理每个数据的任务量远远大于生产者、阻塞队列。

2、生产者和消费者之间解耦合

通过阻塞队列,生产者与消费者之间的联系实际上就变少了,他们的代码这和缓冲区有关,当其中的一个模块挂了或者需要增加消费者\生产者,对其他模块的影响或者修改比较少,利于代码的维护。

缺点

缺点也很明显,多增加了一个缓冲区模块,那么程序响应速度必然会降低!


三、JAVA标准库中阻塞队列的使用方式

常用接口认识

调用方法的选择

在BlockingQueue中,与普通队列一样带有poll和offer两个方法,但是一般都不用这两个方法,因为它们不带有阻塞效果

与之对应的带来了两个新的方法takeput。这两个方法是带有阻塞效果的:

java">public class Queue {public static void main(String[] args) throws InterruptedException {BlockingQueue<Integer> blockingQueue=new ArrayBlockingQueue<Integer>(3);blockingQueue.put(1);blockingQueue.put(1);blockingQueue.put(1);blockingQueue.put(1);//在插入一个主线程就会WAITING}
}

接下来用put和take模拟一个生产者


消费者模型:

java">public class Queue {public static void main(String[] args) throws InterruptedException {BlockingQueue<Integer> blockingQueue=new ArrayBlockingQueue<Integer>(10);Thread thread1=new Thread(()->{int i=0;while(true){i++;System.out.println("生产一个元素:"+i);try {blockingQueue.put(i);} catch (InterruptedException e) {//调用put或者take都需要处理中断异常e.printStackTrace();}}});Thread thread2=new Thread(()->{while(true){try {int i=blockingQueue.take();Thread.sleep(1000);//模拟消费者处理速度慢的情况System.out.println("消费一个元素:"+i);} catch (InterruptedException e) {e.printStackTrace();}}});thread1.start();thread2.start();}
}

执行了大约1分钟后的结果,程序稳定执行:

虽然生产速度要快于消费速度,但是没有出现程序崩溃的情况,最终程序会稳定执行下去,虽然可能会慢一点,这就是阻塞队列的优点和缺点的体现。


四、简单的阻塞队列实现

这里就不用泛型了,直接用一个String类的队列来实现,简单了解它的原理。

底层用数组,使用循环队列(含useSize)创建一个普通队列:

java">class MyBlockingQueue {//用数组实现String[] data = null;//分别表示头、尾、队列长度int head = 0, tail = 0, useSize = 0;/**指向有效元素的指针是左闭右开的,[head,tail)* tail端put进* head端take出*///构造方法,设置最大容量public MyBlockingQueue(int capacity) {this.data = new String[capacity];}//插入方法public void put(String s){//如果队列满了,不做处理if(useSize==data.length)return;data[tail]=s;//直接赋值,因为tail区间是开的tail++;if(tail>=data.length)tail=0;useSize++;//记得长度加一}//取出方法public String take(){//没有就取不出来if(useSize==0)return null;String ret=data[head];head++;if(head>=data.length)head=0;useSize--;return ret;}}

在这个普通队列的基础上,我们要实现阻塞的效果,在线程中,所以还需要对put和take方法进行改造,对方法进行加锁,并使用wait和notify方法来相互之间通信:

java">class MyBlockingQueue {//用数组实现private String[] data = null;//分别表示头、尾、队列长度private volatile int head = 0, tail = 0, useSize = 0;//加volatile没有坏处,虽然出现内存可加性优化概率很低(while循环执行速度并不快)/**指向有效元素的指针是左闭右开的,[head,tail)* tail端put进* head端take出*///构造方法,设置最大容量public MyBlockingQueue(int capacity) {this.data = new String[capacity];}//插入方法public void put(String s) throws InterruptedException {
//        //如果队列满了,不做处理
//        if(useSize==data.length)return;synchronized(this){/**this指的是当前类,创建的一个实例,其他实例对象也可以,只要对象是匹配的*/if(useSize==data.length){this.wait();//让当前线程先睡眠,等其他线程take元素了,然后再唤醒它}data[tail]=s;//直接赋值,因为tail区间是开的tail++;if(tail>=data.length)tail=0;useSize++;//记得长度加一this.notify();//put完了,反过来通知take}}//取出方法public String take() throws InterruptedException {//        //没有就取不出来
//        if(useSize==0)return null;synchronized(this){if(useSize==0){this.wait();}String ret=data[head];head++;if(head>=data.length)head=0;useSize--;this.notify();//take完了,通知一下putreturn ret;}}
}

这个代码其实还有一个小瑕疵,不知道大家有没有发现。

wait方法除了notify方法可以把他唤醒,之前学过的interrupt方法实际上也会把调用了wait方法的线程唤醒然后终止该线程!

倘若一个不小心在put或者take的时候,调用了interrupt方法,那么即使if条件不成立,程序还会继续往下执行(因为Java中断线程是柔和的方式,没有用try catch具体处理中断后的情况,那么程序就会继续往下执行),这就会造成不可预期的后果:

解决这个问题也很简单,直接把if换成while循环即可,即使中断了循环在进入睡眠即可:

java">class MyBlockingQueue {//用数组实现private String[] data = null;//分别表示头、尾、队列长度private volatile int head = 0, tail = 0, useSize = 0;/**指向有效元素的指针是左闭右开的,[head,tail)* tail端put进* head端take出*///构造方法,设置最大容量public MyBlockingQueue(int capacity) {this.data = new String[capacity];}//插入方法public void put(String s) throws InterruptedException {
//        //如果队列满了,不做处理
//        if(useSize==data.length)return;synchronized(this){/**this指的是当前类,创建的一个实例,其他实例对象也可以,只要对象是匹配的*/while(useSize==data.length){this.wait();//让当前线程先睡眠,等其他线程take元素了,然后再唤醒它}data[tail]=s;//直接赋值,因为tail区间是开的tail++;if(tail>=data.length)tail=0;useSize++;//记得长度加一this.notify();//put完了,反过来通知take}}//取出方法public String take() throws InterruptedException {//        //没有就取不出来
//        if(useSize==0)return null;synchronized(this){while(useSize==0){this.wait();}String ret=data[head];head++;if(head>=data.length)head=0;useSize--;this.notify();//take完了,通知一下putreturn ret;}}
}

注意:

以上代码成立的条件是put和take不可能同时调用进入WAITING状态,因为useSize不可能同时满足useSize==0&&useSize==data.length。


http://www.ppmy.cn/embedded/97463.html

相关文章

[000-01-030].Zookeeper学习大纲

我的博客大纲 我的后端学习大纲 第一步&#xff1a;Zookeeper是什么 1.第01节&#xff1a;Zookeeper概述 第二步&#xff1a;Zookeeper怎么使用&#xff1a; 2.1.开发环境搭建 2.第02节 &#xff1a;Zookeeper本地安装3.第03节 &#xff1a;Zookeeper集群操作 2.2.具体使用…

企业级批量无人值守安装

企业级批量无人值守安装 一、批量无人值守安装1.简介PXE工作流程 2.核心技术&#xff08;dhcp、httpd、tftp&#xff09;3.实验3.1 准备环境3.2 防护关闭3.3 软件安装3.4 软件配置DHCP服务设置httpd服务配置tftp服务配置 3.5 编写引导安装相关文件&#xff0c;放到指定位置3.5.…

【漫谈C语言和嵌入式013】函数指针与指针函数详解:概念、区别及实例

在C语言中&#xff0c;理解指针的各种用法是非常关键的&#xff0c;特别是当涉及到更复杂的概念如函数指针和指针函数时。这两者听起来非常相似&#xff0c;但实际上是完全不同的概念。在这篇博客中&#xff0c;我们将探讨函数指针和指针函数的定义、区别以及如何在实际中使用它…

共享经济背景下校园、办公闲置物品交易平台-计算机毕设Java|springboot实战项目

&#x1f34a;作者&#xff1a;计算机毕设匠心工作室 &#x1f34a;简介&#xff1a;毕业后就一直专业从事计算机软件程序开发&#xff0c;至今也有8年工作经验。擅长Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等。 擅长&#xff1a;按照需求定制化开发项目…

yolov8语义分割训练-验证-推理-转化-数据集(代码+教程)

实例分割概述 实例分割是计算机视觉任务的一种&#xff0c;它超越了简单的物体检测&#xff0c;能够识别图像中的单个物体并将其从图像的其余部分分割出来。这种高级别的分割技术不仅能定位物体的位置&#xff0c;还能精确描绘出每个物体的实际形状。 实例分割模型的输出 实…

十五年以来 — 战略性云平台服务的演进路径之全面呈现(含亚马逊、微软和谷歌)

Gartner每年都发布对全球IaaS平台进行评估的魔力象限报告。2023年底&#xff0c;Gartner将此项评估的名称改为“战略性云平台服务”&#xff08;Strategic cloud platform services&#xff09;&#xff0c;尽管其核心仍为IaaS&#xff0c;但是&#xff0c;毫无疑问&#xff0c…

redis字符串若干记录

1、字符串 redis字符串支持二进制安全、redis中的健都为字符串类型、redis中所有的健和值都为redisObject变量 SDS len:已用字节长度 alloc&#xff1a;已申请字节长度 flag&#xff1a;低三位代表sdshdr类型 buf&#xff1a;字符串内容- 根据len判断SDS的类型 &#xff08;…

KBL406-ASEMI、AI智能专用整流桥KBL406

编辑&#xff1a;ll KBL406-ASEMI、AI智能专用整流桥KBL406 型号&#xff1a;KBL406 品牌&#xff1a;ASEMI 封装&#xff1a;KBL-4 批号&#xff1a;2024 现货&#xff1a;50000 正向电流&#xff08;Id&#xff09;&#xff1a;4A 反向耐压&#xff08;VRRM&#xff…