阻塞队列介绍(一)

1 基础概念

1.1 生产者消费者概念

生产者消费者是设计模式的一种。让生产者和消费者基于一个容器来解决强耦合问题。
生产者 消费者彼此之间不会直接通讯的,而是通过一个容器(队列)进行通讯。
所以生产者生产完数据后扔到容器中,不通用等待消费者来处理。
消费者不需要去找生产者要数据,直接从容器中获取即可。
而这种容器最常用的结构就是队列。

1.2 JUC阻塞队列的存取方法

常用的存取方法都是来自于JUC包下的BlockingQueue
生产者存储方法

add(E) // 添加数据到队列,如果队列满了,无法存储,抛出异常
offer(E) // 添加数据到队列,如果队列满了,返回false
offer(E,timeout,unit) // 添加数据到队列,如果队列满了,阻塞timeout时间,如果阻塞一段时间,依然没添加进入,返回false
put(E) // 添加数据到队列,如果队列满了,挂起线程,等到队列中有位置,再扔数据进去,死等!

 消费者取数据方法

remove() // 从队列中移除数据,如果队列为空,抛出异常
poll() // 从队列中移除数据,如果队列为空,返回null,么的数据
poll(timeout,unit) // 从队列中移除数据,如果队列为空,挂起线程timeout时间,等生产者扔数据,再获取
take() // 从队列中移除数据,如果队列为空,线程挂起,一直等到生产者扔数据,再获取

 2 ArrayBlockingQueue

2.1 ArrayBlockingQueue的基本使用

ArrayBlockingQueue在初始化的时候,必须指定当前队列的长度。
因为**ArrayBlockingQueue**是基于数组实现的队列结构,数组长度不可变,必须提前设置数组长度信息。

public static void main(String[] args) throws ExecutionException, InterruptedException, IOException {
// 必须设置队列的长度
ArrayBlockingQueue queue = new ArrayBlockingQueue(4);
// 生产者扔数据
queue.add("1");
queue.offer("2");
queue.offer("3",2,TimeUnit.SECONDS);
queue.put("2");
// 消费者取数据
System.out.println(queue.remove());
System.out.println(queue.poll());
System.out.println(queue.poll(2,TimeUnit.SECONDS));
System.out.println(queue.take());
}

 2.2 生产者方法实现原理

生产者添加数据到队列的方法比较多,需要一个一个查看

2.2.1 ArrayBlockingQueue的常见属性

ArrayBlockingQueue中的成员变量

lock = 就是一个ReentrantLock
count = 就是当前数组中元素的个数
iterms = 就是数组本身
# 基于putIndex和takeIndex将数组结构实现为了队列结构
putIndex = 存储数据时的下标
takeIndex = 去数据时的下标
notEmpty = 消费者挂起线程和唤醒线程用到的Condition(看成sync的wait和notify)
notFull = 生产者挂起线程和唤醒线程用到的Condition(看成sync的wait和notify)

 2.2.2 add方法实现

add方法本身就是调用了offer方法,如果offer方法返回false,直接抛出异常

public boolean add(E e) {
if (offer(e))
return true;
else
// 抛出的异常
throw new IllegalStateException("Queue full");
}

 2.2.3 offer方法实现

public boolean offer(E e) {
// 要求存储的数据不允许为null,为null就抛出空指针
checkNotNull(e);
// 当前阻塞队列的lock锁final ReentrantLock lock = this.lock;
// 为了保证线程安全,加锁
lock.lock();
try {
// 如果队列中的元素已经存满了,
if (count == items.length)
// 返回false
return false;
else {
// 队列没满,执行enqueue将元素添加到队列中
enqueue(e);
// 返回true
return true;
}
} finally {
// 操作完释放锁
lock.unlock();
}
}
//==========================================================
private void enqueue(E x) {
// 拿到数组的引用
final Object[] items = this.items;
// 将元素放到指定位置
items[putIndex] = x;
// 对inputIndex进行++操作,并且判断是否已经等于数组长度,需要归位if (++putIndex == items.length)
// 将索引设置为0
putIndex = 0;
// 元素添加成功,进行++操作。
count++;
// 将一个Condition中阻塞的线程唤醒。
notEmpty.signal();
}

2.2.4 offer(time,unit)方法

生产者在添加数据时,如果队列已经满了,阻塞一会。
● 阻塞到消费者消费了消息,然后唤醒当前阻塞线程
● 阻塞到了time时间,再次判断是否可以添加,不能,直接告辞。

// 如果线程在挂起的时候,如果对当前阻塞线程的中断标记位进行设置,此时会抛出异常直接结束
public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
// 非空检验
checkNotNull(e);
// 将时间单位转换为纳秒
long nanos = unit.toNanos(timeout);
// 加锁
final ReentrantLock lock = this.lock;
// 允许线程中断并排除异常的加锁方式
lock.lockInterruptibly();
try {// 为什么是while(虚假唤醒)
// 如果元素个数和数组长度一致,队列慢了
while (count == items.length) {
// 判断等待的时间是否还充裕
if (nanos <= 0)
// 不充裕,直接添加失败
return false;
// 挂起等待,会同时释放锁资源(对标sync的wait方法)
// awaitNanos会挂起线程,并且返回剩余的阻塞时间
// 恢复执行时,需要重新获取锁资源
nanos = notFull.awaitNanos(nanos);
}
// 说明队列有空间了,enqueue将数据扔到阻塞队列中
enqueue(e);
return true;
} finally {
// 释放锁资源
lock.unlock();
}
}

2.2.5 put方法

如果队列是满的, 就一直挂起,直到被唤醒,或者被中断

public void put(E e) throws InterruptedException {checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
// await方法一直阻塞,直到被唤醒或者中断标记位
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}

2.3 消费者方法实现原理

2.3.1 remove方法

// remove方法就是调用了poll
public E remove() {
E x = poll();
// 如果有数据,直接返回
if (x != null)
return x;
// 没数据抛出异常
else
throw new NoSuchElementException();
}

2.4.2 poll方法

// 拉取数据
public E poll() {
// 加锁操作
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 如果没有数据,直接返回null,如果有数据,执行dequeue,取出数据并返回
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
//==========================================================
// 取出数据
private E dequeue() {
// 将成员变量引用到局部变量
final Object[] items = this.items;
// 直接获取指定索引位置的数据
E x = (E) items[takeIndex];
// 将数组上指定索引位置设置为null
items[takeIndex] = null;// 设置下次取数据时的索引位置
if (++takeIndex == items.length)
takeIndex = 0;
// 对count进行--操作
count--;
// 迭代器内容,先跳过
if (itrs != null)
itrs.elementDequeued();
// signal方法,会唤醒当前Condition中排队的一个Node。
// signalAll方法,会将Condition中所有的Node,全都唤醒
notFull.signal();
// 返回数据。
return x;
}

2.4.3 poll(time,unit)方法

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
// 转换时间单位
long nanos = unit.toNanos(timeout);
// 竞争锁
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 如果没有数据
while (count == 0) {if (nanos <= 0)
// 没数据,也无法阻塞了,返回null
return null;
// 没数据,挂起消费者线程
nanos = notEmpty.awaitNanos(nanos);
}
// 取数据
return dequeue();
} finally {
lock.unlock();
}
}

2.4.4 take方法

public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 虚假唤醒
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}

2.4.5 虚假唤醒

阻塞队列中,如果需要线程挂起操作,判断有无数据的位置采用的是while循环 ,为什么不能换成if
肯定是不能换成if逻辑判断
线程A,线程B,线程E,线程C。 其中ABE生产者,C属于消费者
假如线程的队列是满的

// E,拿到锁资源,还没有走while判断
while (count == items.length)
// A醒了
// B挂起
notFull.await();
enqueue(e);

C此时消费一条数据,执行notFull.signal()唤醒一个线程,A线程被唤醒
E走判断,发现有空余位置,可以添加数据到队列,E添加数据,走enqueue
如果判断是if,A在E释放锁资源后,拿到锁资源,直接走enqueue方法。
此时A线程就是在putIndex的位置,覆盖掉之前的数据,造成数据安全问题

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

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

相关文章

Qt QSpinBox与QDoubleSpinBox总结

QSpinBox 与 QDoubleSpinBox QSpinBox 和 QDoubleSpinBox是常用的数值输入和输出组件&#xff0c;我们将它们统称为 SpinBox。 从SpinBox读取的数据就是数值&#xff08;整数或浮点数&#xff09;&#xff0c;设置数值就可以直接显。QSpinBox 用于输入和输出整数&#xff0c;一…

使用Python的turtle库绘制随机生成的雪花

1.1引言 在这篇文章中&#xff0c;我们将使用Python的turtle库来绘制一个具有分支结构的雪花。该程序使用循环和随机颜色选择来绘制20个不同大小和颜色的雪花。turtle库是一个流行的绘图库&#xff0c;常用于创建图形用户界面和简单的动画。这个代码实现了一个有趣的应用&…

Elasticsearch:ES|QL 查询中的元数据字段及多值字段

在今天的文章里&#xff0c;我来介绍一下 ES|QL 里的元数据字段以及多值字段。我们可以利用这些元数据字段以及多值字段来针对我们的查询进行定制。 ES|QL 源数据字段 ES|QL 可以访问元数据字段。 目前支持的有&#xff1a; _index&#xff1a;文档所属的索引名称。 该字段的…

vue2项目从0搭建(三):配置环境变量及对应的webpack配置

前言 实际业务开发中,一个项目很可能会同时配置好几套环境。 比如:常规开发环境,开发测试环境,正式的测试环境,预发测试环境,客户甲的生产环境,客户乙的生产环境,通用生产环境,独立应用环境,微前端环境,大屏专用环境,移动端环境。 一女多嫁的实际业务场景,就需要我们进行多样…

tensorflow和pytorch都分别存在CPU和GPU版本

TensorFlow和PyTorch都有专门为CPU和GPU优化的版本。它们之间的代码在某些方面有一些不同&#xff0c;但通常可以相对容易地进行转换。以下是一些主要的区别和转换规则&#xff1a; 特性/操作TensorFlowPyTorch转换规则张量创建tf.constant()torch.tensor()创建张量时&#xf…

Android 提示框代码 java语言

在Android中&#xff0c;你可以使用 AlertDialog 类来创建提示框。以下是一个简单的Java代码示例&#xff0c;演示如何创建和显示一个基本的提示框&#xff1a; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; im…

AUTOSAR汽车电子嵌入式编程精讲300篇-基于机器学习的车载 CAN 网络入侵检测(续)

目录 3.2 车载 CAN 总线异常检测技术总结 基于机器学习算法的 CAN 总线入侵检测 4.1 相关知识概述

RocketMq架构和源码解析

NameServer&#xff1a;作为注册中心&#xff0c;提供路由注册、路由踢出、路由发现功能&#xff0c;舍弃强一致&#xff0c;保证高可用&#xff0c;集群中各个节点不会实时通讯&#xff0c;其中一个节点下线之后&#xff0c;会提供另外一个节点保证路由功能。 Broker&#xf…

vatee万腾的科技征途:Vatee独特探索的数字化力量

在数字化时代的浪潮中&#xff0c;Vatee万腾以其独特的科技征途成为引领者。公司在数字化领域的探索之路不仅是技术的创新&#xff0c;更是一种对未知的勇敢涉足&#xff0c;是对新时代的深刻洞察和积极实践。 Vatee万腾通过独特的探索&#xff0c;展示了在数字化征途上的创新力…

数据结构与算法之二叉树: LeetCode 101. 对称二叉树 (Typescript版)

对称二叉树 https://leetcode.cn/problems/symmetric-tree/ 描述 给你一个二叉树的根节点 root &#xff0c; 检查它是否轴对称。 示例 1 1/ | \2 | 2/ \ | / \3 4 | 4 3中间一条线是对称轴 输入&#xff1a;root [1,2,2,3,4,4,3] 输出&#xff1a;true示例 …

Matplotlib子图的创建_Python数据分析与可视化

Matplotlib子图的创建 plt.axes创建子图fig.add_axes()创建子图 plt.axes创建子图 前面已经介绍过plt.axes函数&#xff0c;这个函数默认配置是创建一个标准的坐标轴&#xff0c;填满整张图。 它还有一个可选的参数&#xff0c;由图形坐标系统的四个值构成。这四个值表示为坐…

Spine深入学习 —— 数据

atlas数据的处理 作用 图集&#xff0c;描述了spine使用的图片信息。 结构 page 页块 页块包含了页图像名称, 以及加载和渲染图像的相关信息。 page1.pngsize: 640, 480format: RGBA8888filter: Linear, Linearrepeat: nonepma: truename: 首行为该页中的图像名称. 图片位…

Python武器库开发-前端篇之CSS盒模型(三十一)

前端篇之CSS盒模型(三十一) CSS盒模型是指网页中的每个元素可以看做是一个矩形盒子&#xff0c;该盒子有四个主要部分组成&#xff1a;content、padding、border和margin。其中&#xff1a; content&#xff1a;指盒子中的内容区域&#xff0c;可以包含文本、图像、视频、其他…

RedLock底层源码分析

RedLock底层源码分析 一、Redlock红锁算法 https://redis.io/docs/manual/patterns/distributed-locks/官网说明 1、为什么要学习这个&#xff1f;怎么产生的&#xff1f; ​ 一个很直接的问题&#xff0c;当我使用redis锁的那台机器挂了&#xff0c;出现了单点故障了&#…

游戏开发引擎Cocos Creator和Unity如何对接广告-AdSet聚合广告平台

在游戏开发方面&#xff0c;游戏引擎的选择对开发过程和最终的产品质量有着重大的影响&#xff0c;Unity和Cocos是目前全球两大商用、通用交互内容开发工具&#xff0c;这两款引擎受到广泛关注&#xff0c;本文将从多个维度对两者进行比较&#xff0c;为开发者提供正确的选择建…

Rust10 Building a Multithreaded Web Server [End]

Rust学习笔记 Rust编程语言入门教程课程笔记 参考教材: The Rust Programming Language (by Steve Klabnik and Carol Nichols, with contributions from the Rust Community) Lecture 20: Final Project: Building a Multithreaded Web Server src/main.rs use std::fs; …

84基于matlab的数字图像处理

基于matlab的数字图像处理&#xff0c;数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。 84matlab数字图像处理图像增强 (xiaohongshu.com)https://www.xiaohongshu.com/explore/656219d80000000032034dea

python小数据分析小结及算法实践集锦

在缺乏大量历史数据的新兴技术和产业中&#xff0c;商业分析可能会面临一些挑战。然而&#xff0c;有一些技术和方法可以帮助分析者在数据不充分的情况下进行科学化商业分析&#xff0c;并为决策提供支持。 1. 当面对缺乏大量历史数据的新兴技术和产业时所采常用的技术和方法 …

二进制数据转换成十六进制表示 binascii.hexlify()

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 二进制数据转换成十六进制表示 binascii.hexlify() 选择题 binascii.hexlify()参数的数据类型可以是&#xff1f; import binascii number 11 byte_data number.to_bytes() hex_data bin…

Android : Java中创建线程的几种方式_简单应用

主方法 MainTest.java package com.example.mythread;import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask;public class MainTest {public static void main(String[] data){ // 以下的方…