《javaEE篇》--阻塞队列详解

阻塞队列

阻塞队列概述

阻塞队列也是一种队列,和普通队列一样遵循先进先出的原则,但是阻塞队列相较于普通队列多了两项功能阻塞添加阻塞移除,使得阻塞队列成为一种线程安全的数据结构

  • 阻塞添加:当队列满的时候继续入队就会阻塞等待,直到有线程从队列中取走一个元素之后,队列才能重新添加新元素
  • 阻塞移除:当队列为空时继续出队此时也会阻塞等待,直到有线程在队列中添加新的元素之后,队列才可以继续删除元素

生产者消费者模型 

生产者消费者模型就是通过一个中间容器,来解决生产者和消费者之间的强耦合作用。

生产者和消费者不直接接触,而是由这个中间容器来完成二者之间的交流,生产者将生产的数据直接扔到中间容器里,消费者直接从中间容器里拿数据。

这里的中间容器就相当于是一个仓库,当生产者生产的数据太多时仓库满了,此时再向仓库内输入数据就会阻塞,相反当仓库为空时,消费者在向仓库索要数据,也会发生阻塞。这样就可以很好的平衡两者之间的处理能力。

生产者的生产速率和消费者的消费速率往往是不同的,难免会出现生产速率暴增或消费效率暴增的情况(比如各大电商平台的双11活动),这时如果让生产者和消费者直接对接很有可能就会出现问题(比如服务器挂掉),所以要使用这样一个中间容器来平衡,生产者可以按照自己的速率向容器中填充数据,消费者也可以按照自己的速率拿取数据

阻塞队列的作用

解耦合

把阻塞队列封装成单独的服务器程序,将服务器a接收到的指令传给阻塞队列,再将指令从阻塞队列传到服务器b,此时,耦合就会被降低,如果b这边出现问题,就不会对a产生直接影响

削峰填谷

举个例子🌰:

假如在某电商平台的双11活动,此时用户发送的请求会突然暴增,如果服务器a收到的每一个请求都发给服务器b,那么,a和b所接触到的访问量就一样了,虽然ab的访问量一样,但是可能由于a和b上所跑的业务可能不一样,就会导致单个访问所消耗的硬件资源不一样,最终造成,A可以承受住这些访问量,但是b不可以。比如b要操作数据库,数据库本身就是分布式系统中相对脆弱的环节。

如果此时使用阻塞队列,A将收到的大量请求先写入到队列中,b就可以按照,之前的节奏来处理请求

  • 削峰就相当于阻塞队列帮b承担了来自a的大量访问的压力,B可以按照之前的速度来进行处理,而且像上述峰值的情况一般,不会持续存在,只会短时间出现。
  • 填谷就相当于,峰值之后a的请求量就恢复正常或者降低了,b就可以逐渐的把积压的数据都给处理掉

标准库中的阻塞队列

在 Java 标准库中内置了阻塞队列. 如果我们需要在一些程序中使用阻塞队列, 直接使用标准库中的即可.

  • LinkedBlockingQueue<>    链表实现的阻塞队列
  • LinkedTransferQueue<>    链表实现的无界阻塞队列
  • ArrayBlockingQueue<>      顺序表实现的有界阻塞队列
  • PriorityBlockingQueue<>    支持优先级排序的阻塞队列(由堆实现)
  • synchronousQueue<>    不存储元素的阻塞队列,每次入队操作必须伴随一个出队操作

//比较常用的就是 LinkedBlockingQueue<> 

一般使用的BlockingQueue<>只是一个接口,真正实现的类是LinkedBlockingQueue<> 

阻塞队列常用的阻塞入队操作是put,阻塞出队操作是take

实现阻塞队列

环形队列

博主这里使用数组环形队列来实现一个阻塞队列,环形队列是一种特殊的队列,在逻辑上是环形的数组首尾相连,但是实际上是一个定长的数组,环形队列依然保持队列先入先出的特点。

下面我通过画图来描绘一下

定义一个head表示队头,在定义一个tail表示队尾(实际上是指向队尾的下一个位置),在初始状态下队列为空,head和tail重合

入队列操作就是,先在tail处插入新的元素,然后让tail++。出队列就是,把head位置的数据删除掉,直接让head++就行(逻辑删除),已经删除的数据会被之后插入的数据覆盖掉,当head和tail再一次重和就意味着队列为空了。数组内真正有效的数据就是区间[head,tail)内的数据。

但是这样做还有一个问题,当队列满的时候

此时head和tail就又重合了,队列空的时候他俩重合,队列满的时候他俩也是重合,这样似乎就不太好判断队列究竟是满还是空。这里有两种方法可以解决

  1. 浪费一个格子让tail指向head前一个时,队列就算满了
  2. 专门定义一个变量size来表示队列里的有效元素个数,当size=0时队列为空,size=”数组长度“时为满

两种方法都可以,这里我采用的是第二种方法

代码展示

public class AnnularQueue {private String[] elems =new String[100];//队首private int head = 0;//队尾private int tail = 0;//有效元素个数private int size = 0;//入队方法public void put(String elem) throws InterruptedException {//当队列已满时if(size == elems.length) {return;}//将元素插入队尾elems[tail] = elem;tail++;//超过数组长度后回到数组首位if(tail >= elems.length) {tail = 0;}//有效元素总数加一size++;}//出队方法public String tack() throws InterruptedException {String elem = null;//当队列为空时if(size == 0) {return elem;}//出队(逻辑删除)elem = elems[head];head++;if(head == elems.length) {//回到数组首位head = 0;}//存入后有效元素总数加一size--;//返回取出的元素return elem;}}

实现阻塞队列

之前有提到阻塞队列的特点在于,当队列为满和为空时都会进行阻塞等待,所以我们只需要在刚刚实现的环形队列上加入这个功能就行。

代码展示

class MyBlockingQueue{private String[] data = new String[1000];private volatile int head = 0;private volatile int tail = 0;private volatile int size = 0;public void put(String elem) throws InterruptedException {synchronized (this){//队列是否满了//为了确认wait在唤醒之后,还可以再确认条件是否成立while(size == data.length){//如果是普通队列就直接返回//return;//如果是阻塞队列就直接waitthis.wait();}data[tail] = elem;tail++;size++;//如果tail自增之后到达数组末尾,就让tail重新回到数组开头if(tail == data.length){tail = 0;}//这个notify是用来唤醒因为队列为空而引起的阻塞//take中的waitthis.notify();}}public String take() throws InterruptedException {synchronized (this){//队列是否为空//while(size == 0){//如果是普通队列就直接返回空//return null;//如果是阻塞队列就直接等待this.wait();}//队列不为空,就去返回队首head处数据String elem = data[head];head++;size--;if(head == data.length){head = 0;}//这个notify是用来唤醒,因队列满而阻塞的wait//put中的waitthis.notify();return  elem;}}
}

既然我们要在队列满和空时让队列阻塞,那么就要使用wait来让调用该方法的线程等待阻塞

但是不能就这样一直等待下去,还要使用notify来解除等待,根据最开始的分析,当队列因为出队操作而阻塞时,就要等待其他线程进行入队操作,当队列因为入队操作阻塞时,就要等待其他线程进行出队操作。那么我们在tack和put操作最后各加上一个notify,这样就可以实现上面的功能了。因为如果队列处在阻塞状态下,必然是因为满了或者为空,而且必须是在其他线程完成tack/put操作之后,在可以使用notify解阻塞。

  • put的notify用来唤醒因为tack而引起的阻塞
  • tack的notify用来唤醒因为put而引起的阻塞

 这里还有一个问题wait一定会被notify唤醒吗?或者说只有notify可以唤醒wait吗?

当然不止,还会因为interrupt方法直接中断当前的线程,不过我们刚刚使用了throws InterruptedException,会使线程直接报异常然后结束整个方法,这样是没问题的,但是如果使用try-catch就会处bug了。

此时方法并不会结束,而是会继续往下执行,如果队列已经满了的话,队列的最后一个元素就会被新插入的元素覆盖掉,但是被覆盖的元素并不是一个无效元素,而且此时size也比数组长度大,这样显然是不合理的。

所以在这种情况下我们要注意当前唤醒wait是notify还是interrupt,如果是notify证明此时队列已经不满可以继续插入元素,如果是interrupt唤醒,则此时队列还是瞒着的如果插入元素就会出现问题。

虽然我们刚刚使用的是throws直接抛出异常,这样就算是interrupt唤醒的也没什么大事,但是以防万一,我们还是要处理一下这个问题。

处理的方法也很简单,当wait被唤醒之后再判断一次队列是否为满,要是为满就继续wait,因为wait也可能被interrupt连续唤醒好几次,所以我们直接使用while作为判断语句,直到队列不满时才可以进行新的入队操作

到这里我们简单实现的一个阻塞队列就算完成了,接下来我们使用这个阻塞队列来实现一个简单的生产者消费者模型

实现生产者消费者模型

//实现生产者消费这模式MyBlockingQueue blockingQueue = new MyBlockingQueue();//生产者Thread t1 = new Thread(() -> {int num = 0;while (true){try {//生产数据blockingQueue.put(num + "");//转换为字符System.out.println("生产:" + num);num++;Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});//消费者Thread t2 = new Thread(() -> {while (true){try {//消费数据String elem = blockingQueue.take();System.out.println("消费:" + elem);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();

 //可以通过sleep来调整生产速率和消费速率

运行结果

可以看到我刚刚的代码是消费速率远大于生产速率(生产速率是每秒生产1),所以结果会按照生产速率来执行.

接着我们调整速率,这次是生产速率是按照编译器执行的速度,消费速率是每0.5秒消费1

可以看到生产者瞬间生产了1000个数据,但是消费者任然按照自己的速率去执行,这里阻塞队列就起到了一个削峰填谷的作用

以上就是博主对阻塞队列知识的分享,如果有不懂的或者有其他见解的欢迎在下方评论或者私信博主,也希望多多支持博主之后和博客!!🥰🥰​​​​​​​

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

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

相关文章

UE4 UnrealPak加密功能(配置AES encrypt key)

本文的重点在于如何使用UnrealPak的加密功能&#xff0c;以及相关的UE4源代码学习。本文参考了&#xff1a;https://www.cnblogs.com/shiroe/p/14803859.html 。 设置密钥 在编辑、项目设置中找到下面栏目&#xff0c;并点击“生成新的加密密钥”&#xff0c;就可以为Unreal P…

unity2D游戏开发10生命条脚本

HitPoints 在ScriptableObjects文件夹中创建新的脚本,叫HitPoint using System.Collections; using System.Collections.Generic; using UnityEngine;//创建条目,方便轻松创建HitPoints的实例 [CreateAssetMenu(menuName ="HitPoints")] public class HitPoints :…

锅总介绍CNCF主要目标、全景图及发展历史

一、CNCF简介 云原生计算基金会&#xff08;Cloud Native Computing Foundation&#xff0c;简称 CNCF&#xff09;是一个成立于 2015 年的非营利性组织&#xff0c;隶属于 Linux 基金会。CNCF 的主要目标是通过开源软件推动云原生计算技术的发展和普及&#xff0c;帮助企业更…

四、使用renren-generator生成基本代码

1、打开generator.properties配置文件&#xff0c;修改配置 主要修改包名、模块名、前缀信息 2、修改application.yml配置文件中的数据库信息 3、启动项目 直接访问代码生成器 http://localhost/#generator选择表&#xff0c;点击生成代码即可

怎么使用github上传XXX内所有文件

要将 目录中的所有文件上传到 GitHub&#xff0c;你可以按照以下步骤进行&#xff1a; 创建一个新的 GitHub 仓库 登录到你的 GitHub 账户。 点击右上角的加号&#xff08;&#xff09;&#xff0c;选择 “New repository”。 输入仓库名称&#xff08;例如&#xff1a;202407…

滑动窗口练习6-找到字符串中所有字母异位词

题目链接&#xff1a;**. - 力扣&#xff08;LeetCode&#xff09;** 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找到 s 中所有 p 的 异位词 的子串&#xff0c;返回这些子串的起始索引。不考虑答案输出的顺序。 异位词 指由相同字母重排列形成的字符串&#…

《程序猿入职必会(6) · 返回结果统一封装》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

Profinet从站转TCP/IP协议转化网关(功能与配置)

如何将Profinet和TCP/IP网络连接通讯起来呢?近来几天有几个朋友问到这个问题&#xff0c;那么作者在这里统一说明一下。其实有一个不错的设备产品可以很轻易地解决这个问题&#xff0c;名为JM-DNT-PN。接下来作者就从该设备的功能及配置详细说明一下。 一&#xff0c;设备主要…

el-table合计行更新问题

说明&#xff1a;在使用el-table自带的底部合计功能时&#xff0c;初始界面不会显示合计内容 解决方案&#xff1a;使用 doLayout()方法 updated() {this.$nextTick(() > {this.$refs[inventorySumTable].doLayout();});},完整代码&#xff1a; // show-summary&#xff1a…

Bugku的web题目get,post

1.web基础$_GET http://114.67.175.224:17587/ OK明显的代码审计题目。 让我们看看代码&#xff0c;先get获取what参数变量&#xff0c;如果what变量‘flag’&#xff0c;输出flag。 该题为GET传参&#xff0c;可直接在url后面加参数 在url后加上?whatflag 即可获得flag 2…

科普文:科普文:springcloud之-Hystrix服务容错

Hystrix概念 Hystrix 服务容错保护 的概念和说明 这就是大名鼎鼎的&#xff1a;豪猪 豪猪的英文就是&#xff1a;Hystrix&#xff0c;国外一些大牛的程序员在给自己的架构起名字的时候&#xff0c;往往就这么特别。哪天咱们中国人自己也能写出些架构&#xff0c;咱们就按照中…

2024后端开发面试题总结

一、前言 上一篇离职贴发布之后仿佛登上了热门&#xff0c;就连曾经阿里的师兄都看到了我的分享&#xff0c;这波流量真是受宠若惊&#xff01; 回到正题&#xff0c;文章火之后&#xff0c;一些同学急切想要让我分享一下面试内容&#xff0c;回忆了几个晚上顺便总结一下&#…

【VS2019安装+QT配置】

【VS2019安装QT配置】 1. 前言2. 下载visual studio20193. visual studio2019安装4. 环境配置4.1 系统环境变量配置4.2 qt插件开发 5. Visual Studio导入QT项目6. 总结 1. 前言 前期安装了qt&#xff0c;发现creator编辑器并不好用&#xff0c;一点都不时髦。在李大师的指导下&…

C++画蜡烛图

GPT-4o (OpenAI) 在 C 中绘制蜡烛图通常不像在高级语言&#xff08;如 Python&#xff09;中那么简单&#xff0c;因为 C 并没有内置的图形绘制库。然而&#xff0c;您可以使用一些第三方库来完成这项任务&#xff0c;比如使用 Qt 或者 SFML 等图形库。这里我们以 Qt 库为例&a…

PM2 快速上手指南

PM2是 Node.js 的优秀运行时管理工具&#xff0c;专为简化和优化 Node.js 应用程序的生产部署与运行而设计。 PM2 官网链接: https://pm2.keymetrics.io/ 1.PM2 的优势 持续运行&#xff1a;即使应用出错或崩溃&#xff0c;也能自动重启。负载均衡&#xff1a;智能地自动分…

Linux shell编程学习笔记67: tracepath命令 追踪数据包的路由信息

0 前言 网络信息是电脑网络信息安全检查中的一块重要内容&#xff0c;Linux和基于Linux的操作系统&#xff0c;提供了很多的网络命令&#xff0c;今天我们研究tracepath命令。 Tracepath 在大多数 Linux 发行版中都是可用的。如果在你的系统中没有预装&#xff0c;请根据你的…

WordPress插件介绍页源码单页Html

源码介绍 WordPress插件介绍页源码单页Html源码&#xff0c;这是一款产品介绍使用页面&#xff0c;也可以用来做其他软件或者应用介绍下载页&#xff0c;界面简约美观&#xff0c;源码由HTMLCSSJS组成&#xff0c;双击html文件可以本地运行效果&#xff0c;也可以上传到服务器…

合作伙伴中心Partner Center中添加了Copilot预览版

目录 一、引言 二、Copilot 功能概述 2.1 Copilot 简介 2.2 Copilot 的核心功能 2.3 Copilot 的访问和使用 三、Copilot 的使用方法 3.1 Copilot 功能区域 3.2 Copilot 使用示例 3.2.1 编写有效提示 3.2.2 使用反馈循环 四、负责任的人工智能 4.1 Copilot 结果的可…

UE4如何直接调试Game

某些功能在编辑器里不好调试&#xff0c;例如Pak&#xff0c;就需要直接调试 Game&#xff0c;做法是选择 Game&#xff0c;不要选择Client&#xff0c;加断点&#xff0c;然后点击 Debug 就好了。 断点调试成功&#xff1a; 同时看到界面&#xff1a;

PCIe总线-Linux内核PCIe软件框架分析(十一)

1.简介 Linux内核PCIe软件框架如下图所示&#xff0c;按照PCIe的模式&#xff0c;可分为RC和EP软件框架。RC的软件框架分为五层&#xff0c;第一层为RC Controller Driver&#xff0c;和RC Controller硬件直接交互&#xff0c;不同的RC Controller&#xff0c;其驱动实现也不相…