多线程 - 阻塞式队列

v2-8cffdf8d243386262aee75b587db14bd_b

阻塞队列

阻塞队列,也是一个队列 ~~ 先进先出
实际上有一些特殊的队列,不一定非得遵守先进先出的 ~~ 优先级队列(PriorityQueue)
阻塞队列,也是特殊的队列,虽然也是先进先出的,但是带有特殊的功能: 阻塞

  1. 如果队列为空,执行出队列操作,就会阻塞.阻塞到另一个线程往队列里添加元素(队列不空)为止.

  2. 如果队列满了,执行入队列操作,也会阻塞.阻塞到另一个线程从队列取走元素位置(队列不满)

消息队列,也是特殊的队列.相当于是在阻塞队列的基础上,加上了个"消息的类型”按照制定类别进行先进先出.此时我们谈到的这个消息队列,仍然是一个“数据结构”.

因为这个消息队列,太好用了 ~~ 因此就有大佬把这样的数据结构,单独实现成了一个程序,这个程序,可以同过网络的方式和其它程序进行通信.

这时这个消息队列,就可以单独部署到一组服务器(分布式).存储能力和转发能力都大大提升了.
很多大型项目里,都可以看到这样的消息队列的身影.

此时消息队列,就已经成为了一个可以和MySQL,Redis这种相提并论的一个重要组件(“中间件”)
rabbit mq 就是消息队列中一种典型实现.还有其他的实现. active mq, rocket mq, kafka ……. 都是业界知名的消息队列.

要想认识清楚消息队列,还是得先认识清楚“阻塞队列”

为什么消息队列这么好用了? 和阻塞队列阻塞的特性关系非常大!!!
基于这样的特性,可以实现“生产者消费者模型”

过年.有个环节就是年夜饭,包饺子 ~~ 一家人在一起包饺子 ~~ 有些地方过年是吃汤圆的
包饺子的环节:挨饺子皮+包饺子

两种典型的包法:

  1. 每个人都分别进行擀饺子皮 + 包饺子 操作
  2. 一个人专门负责擀饺子皮,每次擀好一个皮,就放到盖帘上,其他人负责包,每次都从盖帘上取一个皮进行包 => 这种方式就称为生产者消费者模型.

生产者消费者模型

生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。
生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等
待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取.

此时,负责擀饺子皮的人,就是生产者.
负责包饺子的人,就是消费者.
盖帘就是阻塞队列.

如果擀饺子皮的人擀得太慢了,包饺子的人就得等.
如果擀饺子皮的人擀得太快了,包饺子的人一时就包不完,擀饺子皮的人就得停下来等一会儿

image-20231002091352683


生产者消费者模型,能给我们的程序带来两个非常重要的好处!!!

1.实现了发送方和接收方之间的"解耦"

~~ 降低耦合的过程,就叫做“解耦"
耦合两个模块之间的关联关系是强还是弱
~~ 关联越强,耦合越高

开发中典型的场景:服务器之间的相互调用.

image-20231002095819632

image-20231002110305649

3.可以做到“削峰填谷”,保证系统的稳定性

image-20231002111335375

三峡大坝 ~~ 起到的效果,就是削峰填补

洪灾就是雨量达到峰值了,上游水量增长了,大坝把水拦住,让下游仍然有一个比较平缓的流量
到了旱季,三峡大坝开闸放水.

image-20231002120015605

服务器的开发,也和上述这个模型是非常相似的.
上游,就是用户发送的请求.
下游就是一些执行具体业务的服务器.

用户发多少请求?是不可控的.有的时候,请求多,有的时候请求少…
这时就涉及到了,热搜(比如什么微博热搜什么的)这个概念.
那个词会成为热搜,这是和社会现象有关的,比如新冠期间,”疫情”…就是热搜词.
说不定某个瞬间,很多用户都要来给你发起请求 ~~ 服务器处理每个请求,都需要消耗硬件资源,包括不限于(CPU,内存,硬盘,带宽…),如果某个硬件资源达到瓶颈,此时服务器就挂了,这就给系统的稳定性带来风险!!!
使用生产者消费者模型,就是一个有效的手段!!!

使用标准库提供的阻塞队列

public interface BlockingDeque<E> extends BlockingQueue<E>, Deque<E> {}

LinkedBlockingQueue<> (java.util.concurrent) => 基于链表实现的阻塞队列
priorityBlockingQueue<> (java.util.concurrent) => 带有优先级的阻塞队列 ~~ 基于数据结构堆实现的
ArrayBlockingQueue<> (java.utii.concurrent) => 基于数组实现的阻塞队列

Queue提供的方法有三个:

  1. 入队列 ~~ offer
  2. 出队列 ~~ poll
  3. 取队首元素 ~~ peek

阻塞队列主要方法是两个:

  1. 入队列(带有阻塞功能) ~~ put
  2. 出队列(带有阻塞功能) ~~ take
public static void main(String[] args) throws InterruptedException {BlockingDeque<String> blockingDeque = new LinkedBlockingDeque<>();blockingDeque.put("fly0213"); // 入队列String res = blockingDeque.take(); // 出队列. System.out.println(res);res = blockingDeque.take(); // 队列中没有元素了, take, 就会阻塞.System.out.println(res);
}

现在基于标准库的阻塞队列,写一个简单的生产者消费者模型的代码

import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;/*** Created with IntelliJ IDEA.* Description:* User: fly(逐梦者)* Date: 2023-10-02* Time: 14:30*/
public class ThreadDemo22 {public static void main(String[] args) {BlockingDeque<Integer> blockingDeque = new LinkedBlockingDeque<>();// 创建两个线程, 来作为生产者和消费者Thread customer = new Thread(() -> {while (true) {try {Integer result = blockingDeque.take();System.out.println("消费元素: " + result);} catch (InterruptedException e) {throw new RuntimeException(e);}}});customer.start();Thread producer = new Thread(() -> {int count = 0;while(true){try {blockingDeque.put(count);System.out.println("生产元素: "+ count);count++;Thread.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}}});producer.start();}
}

自己实现一个简单的阻塞队列

~~ 通过编写阻塞队列的代码,来更好的理解多线程

实现一个阻塞队列,需要分三步:

  1. 先写一个普通的队列,队列的实现可以基于数组,也可以基于链表(易于实现头删/尾插).
  2. 加上线程安全.
  3. 加上阻塞功能.

注: 基于链表实现普通队列的时候,需要头删和尾插的时间复杂度都是O(1).
链表头删的时间复杂度本来就是O(1),无需注意,只是链表的尾删操作时间复杂度要实现O(1),需要用一个额外的引用,记录当前的尾结点 ~~ 相关代码没什么难度,博主就不做讲解了!

用数组循环对列的方式来实现阻塞队列
image-20231002174113723

1. 实现普通队列的代码

/*** Created with IntelliJ IDEA.* Description:* User: fly(逐梦者)* Date: 2023-10-02* Time: 15:08*/// 自己写的阻塞队列
// 注: 此处不考虑泛型, 直接使用 int 来表示元素类型
class MyBlockingQueue {private int[] items = new int[1000];private int head = 0;private int tail = 0;private int size = 0;// 入队列public void put(int value) {if (size == items.length) {// 队列满了, 不能继续插入return;}items[tail] = value;tail++;// 对 tail 进行处理// 第一种写法// tail = tail % items.length;// 第二种写法 ( 更推荐, 可读性更高 & % 在效率上没有优势 )if (tail >= items.length) {tail = 0;}size++;}// 出队列public Integer take() {if (size == 0) {// 队列空, 不能出队列return null;}int result = items[head];head++;if (head >= items.length) {head = 0;}size--;return result;}
}public class ThreadDemo23 {public static void main(String[] args) {MyBlockingQueue queue = new MyBlockingQueue();queue.put(1);queue.put(2);queue.put(3);queue.put(4);int result = 0;result = queue.take();System.out.println("result = " + result);result = queue.take();System.out.println("result = " + result);result = queue.take();System.out.println("result = " + result);result = queue.take();System.out.println("result = " + result);}
}

2. 阻塞功能 ~~ 意味着队列要在多线程环境下使用

保证线程安全,主要是就是加上锁 ~~ 使用 synchronized 包裹put()take()方法里的所有代码,当然, synchronized 加到方法上,也是可以的.

// 自己写的阻塞队列
// 注: 此处不考虑泛型, 直接使用 int 来表示元素类型
class MyBlockingQueue {private int[] items = new int[1000];private int head = 0;private int tail = 0;private int size = 0;// 入队列public void put(int value) {synchronized (this) {if (size == items.length) {// 队列满了, 此时要产生阻塞// return;try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}items[tail] = value;tail++;// 对 tail 进行处理// 第一种写法// tail = tail % items.length;// 第二种写法 ( 更推荐, 可读性更高 & % 在效率上没有优势 )if (tail >= items.length) {tail = 0;}size++;// 这个 notify 唤醒 take 中的 waitthis.notify();}}// 出队列public Integer take() {int result = 0;synchronized (this) {if (size == 0) {// 队列空, 此时也需要阻塞try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}return null;}result = items[head];head++;if (head >= items.length) {head = 0;}size--;// 唤醒 put 中的 waitthis.notify();}return result;}
}

image-20231002175701861

这两个线程中的 wait 是否可能会同时触发 ???
~~ 如果同时触发了,显然就不能正确相互唤醒了
答案是否定的,针对同一个队列,不能够既是满,又是空,除非它跟薛定谔的猫一样,嘿嘿!!!

3.上述代码还有一个瑕疵

if (size == items.length) { this.wait(); }

当wait被唤醒的时候,此时if的条件,一定就不成立了嘛??
具体来说, put中的 wait 被唤醒后,要求队列没有满,
但是 wait 被唤醒了之后,队列一定是不满的嘛?
在当前代码中,虽然不会出现这种情况.当前代码一定是取元素成功才唤醒,每次取元素都会唤醒.
但是稳妥起见,最好的办法,是wait返回之后再次判定一下,看此时的条件是不是具备了!!!
例子: 类似于,早上起床上课,要定个8:20的闹钟.正常情况下,闹钟响了,就该起床了.
但是如果7:00就醒了,就发现,距离上课还早(条件尚未满足),还可以再睡会(每次醒了,应该都看下时间,判定一下当前是否满足了起床的条件).

if (size == items.length) {this.wait();      if (size == items.length) {this.wait();} 
}

这么写是进行了二次判定了,但是并不合适.可能第二次wait被唤醒,可能还是条件不具备~~

4.完整版的代码


/*** Created with IntelliJ IDEA.* Description:* User: fly(逐梦者)* Date: 2023-10-02* Time: 15:08*/// 自己写的阻塞队列
// 注: 此处不考虑泛型, 直接使用 int 来表示元素类型
class MyBlockingQueue {private int[] items = new int[1000];private int head = 0;private int tail = 0;private int size = 0;// 入队列public void put(int value) {synchronized (this) {while (size == items.length) { // 标准库就建议这么写!!!// 队列满了, 此时要产生阻塞// return;try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}items[tail] = value;tail++;// 对 tail 进行处理// 第一种写法// tail = tail % items.length;// 第二种写法 ( 更推荐, 可读性更高 & % 在效率上没有优势 )if (tail >= items.length) {tail = 0;}size++;// 这个 notify 唤醒 take 中的 waitthis.notify();}}// 出队列public Integer take() {int result = 0;synchronized (this) {while (size == 0) {// 队列空, 此时也需要阻塞try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}result = items[head];head++;if (head >= items.length) {head = 0;}size--;// 唤醒 put 中的 waitthis.notify();}return result;}
}public class ThreadDemo23 {public static void main(String[] args) {MyBlockingQueue queue = new MyBlockingQueue();queue.put(1);queue.put(2);queue.put(3);queue.put(4);int result = 0;result = queue.take();System.out.println("result = " + result);result = queue.take();System.out.println("result = " + result);result = queue.take();System.out.println("result = " + result);result = queue.take();System.out.println("result = " + result);}
}

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

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

相关文章

软件测试之Python基础学习

目录 一、Python基础 Python简介、环境搭建及包管理 Python简介 环境搭建 包管理 Python基本语法 缩进(Python有非常严格的要求) 一行多条语句 断行 注释 变量 基本数据类型(6种) 1. 数字Number 2. 字符串String 3. 列表List 4. 元组Tuple 序列相关操作方法 …

gitlab配置webhook限制提交注释

一、打开gitlab相关配置项 vim /etc/gitlab/gitlab.rb gitlab_shell[custom_hooks_dir] "/etc/gitlab/custom_hooks" 二、创建相关文件夹 mkdir -p /etc/gitlab/custom_hooks mkdir -p /etc/gitlab/custom_hooks/post-receive.d mkdir -p /etc/gitlab/custom_h…

[Linux 基础] 一篇带你了解linux权限问题

文章目录 1、Linux下的两种用户2、文件类型和访问权限&#xff08;事物属性&#xff09;2.1 Linux下的文件类型2.2 基本权限2.3 文件权限值的表示方法&#xff08;1&#xff09;字符表示方法&#xff08;2&#xff09;8进制数值表示方法 2.4 文件访问权限的相关设置方法(1) chm…

《 新手》web前端(axios)后端(java-springboot)对接简解

文章目录 <font color red>1.何为前后端对接?2.对接中关于http的关键点2.1. 请求方法2.2. 请求参数设置简解&#xff1a; 3.对接中的跨域(CROS)问题**为什么后端处理跨域尽量在业务之前进行&#xff1f;**3.总结 1.何为前后端对接? “前后端对接” 是指前端和后端两个…

【VR】【unity】如何在VR中实现远程投屏功能?

【背景】 目前主流的VD应用,用于娱乐很棒,但是用于工作还是无法效率地操作键鼠。用虚拟键盘工作则显然是不现实的。为了让自己的头显能够起到小面积代替多显示屏的作用,自己动手开发投屏VR应用。 【思路】 先实现C#的投屏应用。研究如何将C#投屏应用用Unity 3D项目转写。…

【开发篇】十三、J2cache缓存框架

文章目录 1、介绍2、二级缓存下数据的读取与更新3、整合4、使用举例5、配置的相关说明6、小结 1、介绍 J2cache是一个缓存整合框架&#xff0c;可以提供缓存的整合方案&#xff0c;使各种缓存搭配使用&#xff0c;自身不提供缓存功能。 J2cache是一个两次缓存的框架 第一级缓存…

国庆中秋特辑(五)MySQL如何性能调优?下篇

目录 5.数据库维护6. 数据库调优工具7.数据库架构优化8.代码层面优化9. 硬件层面优化10. 数据库安全 MySQL 性能优化是一项关键的任务&#xff0c;可以提高数据库的运行速度和效率。以下是一些优化方法&#xff0c;包括具体代码和详细优化方案。 接下来详细介绍&#xff0c;共有…

第1篇 目标检测概述 —(3)YOLO系列算法

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。YOLO&#xff08;You Only Look Once&#xff09;系列算法是一种目标检测算法&#xff0c;主要用于实时物体检测。相较于传统的目标检测算法&#xff0c;YOLO具有更快的检测速度和更高的准确率。YOLO系列算法的核心思想是将…

企业怎样选择适合的服务器租用?

随着互联网技术的发展&#xff0c;如何选择企业需要的服务器租用来满足需求是很多企业目前在考虑的问题&#xff0c;今天就让小编来给大家讲一讲吧&#xff01; 确定好服务器的规模和用途。企业首先根据自身的业务情况选择服务器的数量和规模还有性能&#xff0c;小型企业可以…

python复习

1.python属于解释型语言&#xff0c;解释器逐行解释每一句代码&#xff0c;然后执行 编译型语言需要由编译器生成最终可执行文件再执行 2. #单行注释""" 多行注释 """ 注释快捷键ctrl/ 3.变量是在计算机语言中能储存计算结果或表示某个数据…

计算机竞赛 深度学习机器视觉车道线识别与检测 -自动驾驶

文章目录 1 前言2 先上成果3 车道线4 问题抽象(建立模型)5 帧掩码(Frame Mask)6 车道检测的图像预处理7 图像阈值化8 霍夫线变换9 实现车道检测9.1 帧掩码创建9.2 图像预处理9.2.1 图像阈值化9.2.2 霍夫线变换 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分…

1300*C. Rumor(并查集贪心)

解析&#xff1a; 并查集&#xff0c;求每个集合的最小费用。 每次合并集合的时候&#xff0c;根节点保存当前集合最小的费用。 #include<bits/stdc.h> using namespace std; #define int long long const int N1e55; int n,m,a[N],p[N],cnt[N]; int find(int x){retur…

S5PV210裸机(二):看门狗,栈,icache,SoC时钟,重加载,led

看门狗 电子设备会跑飞或者死机,需要设备自动复位,看门狗是SoC内部定时器,规定时间内需要重新置位,如果没有系统会被强制复位 WTCON&#xff08;0xE2700000&#xff09;&#xff0c;bit5是开关&#xff1a;0关&#xff0c;1开 汇编设置栈和调用C C运行过程中局…

【VINS】苹果手机采集单目相机+IMU数据离线运行VINS-Mono

0.准备工作 开个新坑&#xff0c;之前用Android手机做过离线采集数据的实验&#xff0c;这次用IPhone来测试&#xff01; 1.虚拟机配置Mac OS 下载一个Mac OS 的ios镜像&#xff0c;打开虚拟机按照跟Ubuntu差不多的方式安装&#xff0c;但是发现没有Mac OS的入口。 因为VMwa…

前端两年半,CSDN创作一周年

文章目录 一、机缘巧合1.1、起因1.2、万事开头难1.3、 何以坚持&#xff1f; 二、收获三、日常四、憧憬 五、总结 一、机缘巧合 1.1、起因 最开始接触CSDN&#xff0c;还是因为同专业的同学&#xff0c;将计算机实验课的实验题&#xff0c;记录总结并发在了专业群里。后来正式…

【深入了解Java String类】

目录 String类 常用方法 字符串的不可变性 String的内存分析 StringBuilder类 解释可变和不可变字符串 常用方法 面试题&#xff1a;String&#xff0c;StringBuilder&#xff0c;StringBuffer之间的区别和联系 String类的OJ练习 String类 【1】直接使用&#xff0c…

Arcgis提取玉米种植地分布,并以此为掩膜提取遥感影像

Arcgis提取玉米种植地分布上&#xff0c;并以此为掩膜提取遥感影像 一、问题描述 因为之前反演是整个研究区&#xff0c;然而土地利用类型有很多类&#xff0c;只在农田或者植被上进行反演&#xff0c;需要去除水体、建筑等其他类型&#xff0c;如何处理得到下图中只有耕地类…

【新版】系统架构设计师 - 层次式架构设计理论与实践

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 文章目录 架构 - 层次式架构设计理论与实践考点摘要层次式体系结构概述表现层框架设计MVC模式MVP模式MVVM模式使用XML设计表现层表现层中UIP设计思想 中间层架构设计业务逻辑层工作流设计业务逻辑层设计 数据访问层…

热点文章采集-热点资讯采集工具免费

在信息时代&#xff0c;掌握热点资讯、了解热门时事、采集热门文章是许多自媒体从业者和信息追踪者的重要任务。然而&#xff0c;这并不是一项容易的任务。信息的海洋庞大而繁杂&#xff0c;要从中捞取有价值的热点和文章需要耗费大量时间和精力。 热点资讯采集&#xff1a;信息…

矢量图形编辑软件illustrator 2023 mac特点介绍

illustrator 2023 mac是一款矢量图形编辑软件&#xff0c;用于创建和编辑排版、图标、标志、插图和其他类型的矢量图形。 illustrator mac软件特点 矢量图形&#xff1a;illustrator创建的图形是矢量图形&#xff0c;可以无限放大而不失真&#xff0c;这与像素图形编辑软件&am…