阻塞式队列

news/2024/4/19 3:53:25/

在这里插入图片描述

文章目录

  • 一、阻塞队列
    • 阻塞队列的概念
    • 阻塞队列相关类和方法
    • 生产者消费者模型
  • 二、自定义实现阻塞队列
    • 自定义实现循环队列
    • 自定义实现阻塞队列
    • 生产者消费者模型体验

一、阻塞队列

阻塞队列的概念

队列我们并不默认,一提起队列,我们立马就能想到 "先进先出"的特性。
今天我们就来学习一下特殊的队列: 阻塞队列,它具有下面两个特性:

  1. 如果队列为空,执行出队列操作时,就会阻塞,直到另一个线程往队列里添加元素(队列不为空时)
  2. 如果队列为满,执行入队列操作时,就会阻塞,直到另一个线程从队列里取走元素
    (队列不为满时)

阻塞队列相关类和方法

在这里插入图片描述
我们的JUC下为我们提供了一个泛型接口BlockingQueue.
在这里插入图片描述
同时也提供了一下具体的实现类:

ArrayBlockingQueue : 一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue : 一个由链表结构组成的有界阻塞队列。
PriorityBlockingQueue : 一个支持优先级排序的无界阻塞队列。
LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列。
LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。
SynchronousQueue: 一个不存储元素的阻塞队列。

同时也为我们提供了一些操作阻塞队列的方法。

方法作用
void put()带有阻塞特性的入队操作
E take()带有阻塞特性的出队特性
boolean contains(Object o)判断阻塞队列是否包含某元素

我们来简单的使用一下阻塞队列

public static void main(String[] args) throws InterruptedException {BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();queue.put(1);queue.put(2);Integer take = queue.take();System.out.println("取出阻塞队列中的元素: " + take);take = queue.take();System.out.println("取出阻塞队列中的元素: " + take);take = queue.take();System.out.println("取出阻塞队列中的元素: " + take);}

在这里插入图片描述
我们可以发现我们阻塞队列只放入了2个元素,当我们取元素时,取第三个时,因为阻塞队列没有元素,所以一直在阻塞等待。

生产者消费者模型

为什么会有生产者消费者模型?
在我们多线程编程种,为了解决生产者(生产数据的线程)和消费者(消费数据的线程)之间的执行速度的差异和解决生产者和消费者之间的强耦合关系。
在这里插入图片描述
通过在生产者和消费者之间增加一个队列来避免生产者和消费者之间的直接通信,生产者将数据直接添加到队列中,消费者直接从队列中取数据处理。
那么这样的模型有什么好处呢?

1.降低生产者和消费者之间的耦合度
2.增加一个阻塞队列,平衡生产者和消费者之间的处理能力
在这里插入图片描述
比如我们的双11,12的购物狂欢节,某一时刻的秒杀活动,我们的服务器A某一时刻会向服务器B发送巨量的强求数据,但是我们的服务器处理速度有限,一次性招架不住这么多请求,有可能服务器之间就崩了。
在这里插入图片描述
我们使用生产者消费者模型时,就不会受到影响,即使服务器的请求暴涨,但是他发送到阻塞队列中,我们的服务器B按照自己的处理速度去阻塞队列中取元素,所以不会产生实质性的影响。

public static void main(String[] args) {BlockingQueue<Integer> queue = new LinkedBlockingQueue<>();Thread customer = new Thread(() -> {while(true) {try {int val = queue.take();System.out.println("消费者消费了: " + val);} catch (InterruptedException e) {e.printStackTrace();}}});customer.start();Thread producer = new Thread(() -> {for (int i = 0; i < 10; i++) {try {queue.put(i);} catch (InterruptedException e) {throw new RuntimeException(e);}}});producer.start();}

在这里插入图片描述

二、自定义实现阻塞队列

我们的阻塞队列可以由链表实现,也可以由数组实现,由于链表实现比较简单,尾插头删或者头插尾删即可,这里我们重点介绍数组实现阻塞队列,大家也可以先去学习一下队列这篇文章:java详解队列

自定义实现循环队列

在这里插入图片描述
在实现循环队列时,我们主要面临的问题是,什么情况下队列为空,什么情况下队列为满,在判断满时:我们有两种方案,定义一个size变量,如果等于0为空,等于队列容量为满,这种过于简单,我们采用浪费一个空间的办法,如果head == tail队列为空,如果tail的下一个位置为head为满。

public class MyCircularQueue {//定义队列,默认大小50private int[] num = new int[50];//定义队头指针private int head;//定义队尾指针private int tail;//记录队列元素private int size;//入队public void put(int val) {if(size == num.length) {//队列为满return;}num[tail] = val;tail++;// 等价tail = tail % num.lengthif(tail >= num.length) {tail = 0; //这样的写法,可读性较高}size++;}//出队public Integer take() {if(size == 0) {//队列为空return null; }int ret = num[head];head++;//等价 head = head % num.lengthif(head >= num.length) {head = 0;}size--;return ret;}
}

自定义实现阻塞队列

因为我们的阻塞队列基本都是在多线程情况下使用的,我们的put和take方法都涉及了多线程的读写操作,所以我们加锁。

public class MyBlockingQueue {//定义队列,默认大小50private int[] num = new int[50];//定义队头指针private int head;//定义队尾指针private int tail;//记录队列元素private int size;//入队public void put(int val) {synchronized (this) {if(size == num.length) {//队列为满return;}num[tail] = val;tail++;// 等价tail = tail % num.lengthif(tail >= num.length) {tail = 0; //这样的写法,可读性较高}size++;}}//出队public Integer take() {synchronized (this) {if(size == 0) {//队列为空return null;}int ret = num[head];head++;//等价 head = head % num.lengthif(head >= num.length) {head = 0;}size--;return ret;}}
}

阻塞队列,其最重要的功能就是阻塞,既然我们要实现阻塞功能,我们这里的业务场景需要,主要使用wait 和 notify实现阻塞等待,以及唤醒。我们需要在两处阻塞

  1. 当我们take时,队列为空,我们需要wait方法使其阻塞,直到有元素入队列时,在notify将其唤醒。
  2. 当我们put时,队列为满,我们需要wait方法使其阻塞,直到有元素出队列时,在notify将其唤醒。
public class MyBlockingQueue {//定义队列,默认大小50private int[] num = new int[50];//定义队头指针private int head;//定义队尾指针private int tail;//记录队列元素private int size;//入队public void put(int val) throws InterruptedException {synchronized (this) {if(size == num.length) {//队列为满,阻塞等待this.wait();}num[tail] = val;tail++;// 等价tail = tail % num.lengthif(tail >= num.length) {tail = 0; //这样的写法,可读性较高}size++;//唤醒take方法中因队列为空阻塞的线程this.notify();}}//出队public Integer take() throws InterruptedException {synchronized (this) {if(size == 0) {//队列为空,阻塞等待this.wait();}int ret = num[head];head++;//等价 head = head % num.lengthif(head >= num.length) {head = 0;}size--;//唤醒put方法中因队列为满阻塞的线程this.notify();return ret;}}
}

在这里插入图片描述
上述代码,我们已经基本实现了阻塞队列的功能,但是有一点美中不足的地方,我们的wait被唤醒之后,一定能确保if的条件一定是不满足的吗?
虽然这里可能是肯定的,但我们是一个逻辑严密的程序员,我们在被唤醒的时候在去判断一下是否满足更为妥善。
在这里插入图片描述
我们wait的源码中,也是这样建议的,所以我们修改完善一下。

//入队public void put(int val) throws InterruptedException {synchronized (this) {while(size == num.length) {//队列为满,阻塞等待this.wait();}num[tail] = val;tail++;// 等价tail = tail % num.lengthif(tail >= num.length) {tail = 0; //这样的写法,可读性较高}size++;//唤醒take方法中因队列为空阻塞的线程this.notify();}}//出队public Integer take() throws InterruptedException {synchronized (this) {while(size == 0) {//队列为空,阻塞等待this.wait();}int ret = num[head];head++;//等价 head = head % num.lengthif(head >= num.length) {head = 0;}size--;//唤醒put方法中因队列为满阻塞的线程this.notify();return ret;}}

在这里插入图片描述
在这里插入图片描述

生产者消费者模型体验

我们用我们自定义实现的阻塞队列体验一下生产者消费者模型。

public static void main(String[] args) {MyBlockingQueue queue = new MyBlockingQueue();Thread producer = new Thread(() -> {int count = 0;while (true) {try {queue.put(count);System.out.println("生产了" + count);Thread.sleep(50);count++;} catch (InterruptedException e) {e.printStackTrace();}}});producer.start();Thread customer = new Thread(() -> {while (true) {try {int val = queue.take();System.out.println("消费了: " + val);} catch (InterruptedException e) {e.printStackTrace();}}});customer.start();}

在这里插入图片描述
这样就实现了一个源源不断的生产者消费者模型。这些内容大家还需要多加练习。


http://www.ppmy.cn/news/9792.html

相关文章

实验二十一 配置NAT

实验二十一 配置NAT实验要求&#xff1a; 静态NAT: 在Router的公网侧接口GE0/0/1下配置静态NAT&#xff0c;将私有 IP地址 192.168.0.2与公有IP地址202.10.1.3绑定起来。 NAT SERVER的配置 动态NAT和easy IP的配置网络拓扑图&#xff1a;操作步骤&#xff1a;一、静态NAT1、配置…

用纯C实现单链表

前言 什么是单链表&#xff1f;链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。链表的创建 需要创建一个小项目工程 创建三个文件 ⭐SListNode.h放单链表的头文件&#xff0c;函数声明 ⭐SListNode.c放单…

微信公众号调用扫一扫功能

手把手教你调用微信扫一扫&#xff0c;三分钟包会_前端人的博客-CSDN博客_调用微信扫一扫 第一次搞公众号&#xff0c;还以为跟上回调用企业微信扫一扫一样。。。调起扫一扫功能的过程自然是不同的&#xff0c;要注意的地方还挺多&#xff0c;记录一下 。 其实&#xff0c;在使…

高一数学试题-2022年秋期末试卷

一、选择题 已知集合A{x∈N∣−2<x<52}A \{x \in \mathbf{N}| -2 < x < \frac{5}{2}\}A{x∈N∣−2<x<25​}&#xff0c;B{−2,−1,0,1,2,4}B \{-2, -1, 0, 1, 2, 4\}B{−2,−1,0,1,2,4}&#xff0c;则A∩BA \cap BA∩B A. {−1,0,1,2}\{-1, 0, 1, 2\}{−…

「软技能|真・复盘」复盘不是总结也不是批斗,当我们聊复盘的时候我们在干什么

本文主要介绍复盘的起源并由此延伸出当我们在做工作和生活的复盘时我们的目标以及如果要使得复盘有效我们该如何做。 文章目录复盘最开始用来指代什么&#xff1f;复盘需要哪些准备&#xff1f;复盘分成哪几个步骤&#xff1f;直面结果时我们要考虑事情有哪些&#xff1f;如何分…

你可以不用Git,但不能不会Git(一)概述

目录 一.什么是Git 二.为什么要使用Git 三.Git和SVN对比 四.Git工作流程 五.Git下载与安装 一.什么是Git Git历史 很多人都知道&#xff0c;林纳斯托瓦兹在1991年创建了开源的Linux&#xff0c;从此&#xff0c;Linux系统不断发展&#xff0c;已经成为最大的服务器系统…

缓冲区的深刻理解

代码&&现象 先来看一份代码 #include <stdio.h> #include <string.h> #include <unistd.h> int main() {//C Libraryprintf("hello printf\n");fprintf(stdout, "hello fprintf\n");const char *s1 "hello fwrite\n&quo…

网络安全之URL介绍

目录 网络安全之URL介绍 定义 资源&#xff1a; 组成部分 协议&#xff08;scheme&#xff09; 主机&#xff08;host&#xff09; 端口 路径&#xff08;path&#xff09; 查询参数&#xff08;parameter&#xff09; 锚点&#xff08;anchor&#xff09; URL…

【数组】leetcode59.螺旋矩阵II(C/C++/Java/Js)

leetcode59.螺旋矩阵II1 题目2 思路3 代码3.1 C版本3.2 C版本3.3 Java版本3.4 JavaScript版本4 总结&#xff1a;1 题目 题源链接 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1…

uniapp中引入vant Weapp

Vant Weapp官&#xff1a;https://vant-contrib.gitee.io/vant-weapp/#/home 步骤一&#xff1a;下载vant组件插件 从github上下载该插件https://github.com/youzan/vant-weapp 只要这个dist文件夹&#xff0c;把dist重命名为vant&#xff1b; 步骤二&#xff1a; 与pages…

【C++】stack、queue和deque

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《吃透西嘎嘎》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;stack 的…

论 G1 收集器的架构和如何做到回收时间用户设定

目录G1 概念JVM的内存分代假设让用户设置应用的暂停时间G1 概念 G1其实是Garbage First的意思&#xff0c;它不是垃圾优先的意思&#xff0c;而是优先处理那些垃圾多的内存块的意思。 在大的理念上&#xff0c;它还是遵循JVM的内存分代假设。 JVM的内存分代假设 JVM的内存分代…

Go语言context包源码剖析

context包的作用 context包是在go1.7版本中引入到标准库中的 context可以用来在goroutine之间传递上下文信息&#xff0c;相同的context可以传递给运行在不同goroutine中的函数&#xff0c;上下文对于多个goroutine同时使用是安全的&#xff0c;context包定义了上下文类型&am…

【C语言】交换奇偶位和 offsetof 宏的实现

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《阿亮爱刷题》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录&#x1f449;交换奇偶…

Trie 字典树

Trie Trie&#xff0c;又称字典树或前缀树。是一棵有根的多叉树。用于高效存储和查找字符串集合。 字典树从根到树上某一结点的路径就是一个字符串。 一棵字典树的构造过程图解&#xff1a; 字典树的度和字符集有关&#xff0c;英文字符集是26个字母&#xff0c;那么字典树的…

Redis过期键删除策略

Redis过期键删除策略 第一章 Redis单线程为什么这么快&#xff1f; 第二章 Redis的持久化机制 第三章 Redis过期键的删除策略 Redis过期键删除策略Redis过期键删除策略前言一、惰性过期二、定时过期&#xff08;Redis中没有用到&#xff09;三、定期清理总结前言 想必大家都直…

Android 虚拟分区详解(四) 编译开关

Android Virtual A/B 系统简称 VAB,我将其称为虚拟分区。 本系列文章基于 Android R(11) 进行分析,如果没有特别说明,均基于代码版本 android-11.0.0_r46 请已经购买《Android 虚拟分区》专栏的朋友加我 wx 进 "虚拟分区专栏 VIP 答疑"群,作为本专栏文章的附加服…

pytest学习和使用16-HTML报告如何生成?(pytest-html)

16-HTML报告如何生成&#xff1f;&#xff08;pytest-html&#xff09;1 插件介绍2 pytest-html安装3 生成报告3.1 插件执行方式3.2 执行效果3.3 指定报告生成的路径4 合并css5 报告中的行显示设置6 报告增强6.1 自定义css6.2 报告标题6.3 环境6.4 其他摘要信息6.5 Extra内容6.…

【Linux修炼】12.深入了解系统文件

每一个不曾起舞的日子&#xff0c;都是对生命的辜负。 文件fd一. 重新谈论文件1. 共识的问题2. 重谈C语言文件操作2.1 概要2.2 C语言文件实操2.3 OS接口open的使用&#xff08;比特位标记&#xff09;2.4 写入操作2.5 追加操作2.6 只读操作二. 如何理解文件1. 提出问题2. 文件描…

【CSDN 年终总结】CSDN的进阶之路—— “1+1=王”的2022总结

正文之前打个广告&#xff0c;我正在参加年度博客之星评选&#xff0c;请大家帮我投票打分&#xff0c;您的每一分都是对我的支持与鼓励。⭐ ⭐ ⭐ ⭐ ⭐https://bbs.csdn.net/topics/611386885?spm1001.2014.3001.6953 2022我在CSDN 2022 在CSDN是持续输出&#xff0c;持续…