LinkedBlockingQueue

基本的入队出队

初始化

public class LinkedBlockingQueue<E> extends AbstractQueue<E>implements BlockingQueue<E>, java.io.Serializable {// 静态内部类 Node,用于存储队列元素及维护节点间关系static class Node<E> {// 存储的实际元素,泛型 E 类型E item;/*** next 字段的三种可能情况说明:* - 真正的后继节点:正常队列链接场景,指向下一个节点* - 自己(this):出队操作时的特殊标记,用于辅助处理并发下的节点状态* - null:表示当前节点是队列的最后一个节点,没有后继*/Node<E> next;// 构造方法,初始化节点时设置存储的元素Node(E x) {item = x;}}}

初始化链表时,last 和 head 都指向新建的 Node<E>(null) 。该节点是 Dummy 节点(哑节点、哨兵节点) ,作用是占位,其 item 字段值为 null ,用于辅助 LinkedBlockingQueue 链表结构的初始化与操作(比如方便统一处理队列空、头尾指针等情况 )。

入队

当一个节点入队last = last.next = node;

再来一个节点入队last = last.next = node;

出队

// 出队核心逻辑(基于 LinkedBlockingQueue 链表结构)
public E dequeue() {// 以下是关键出队步骤,实际源码会结合锁、条件变量等保证线程安全,这里聚焦节点操作逻辑Node<E> h = head; // 1. 获取真正存储元素的首节点(head 是 dummy 节点,其 next 指向第一个有效节点)Node<E> first = h.next; // 2. 断开原 head 节点的链接,并让其 next 指向自身,辅助 GC 回收(避免内存泄漏)h.next = h; // 3. 更新 head 为真正的首节点(first),完成出队后队列头指针迁移head = first; // 4. 取出首节点存储的元素(队列要弹出的元素)E x = first.item; // 5. 清空首节点的元素引用(帮助 GC,断开元素引用链)first.item = null; // 6. 返回弹出的元素return x; 
}
  1. 保存头节点:(h = head)
    用变量 h 暂存队列当前的 head(dummy 节点 )。

  2. 获取有效首节点:(first = h.next)
    通过 h.next 拿到真正存储元素的第一个有效节点 first

  3. 断开旧头节点链接:(h.next = h
    让 h.next = h(自己指向自己 ),切断旧头节点与队列的关联,辅助垃圾回收。

  4. 更新头节点:(head = first)
    把 head 指向 first,完成队列头指针迁移,保证后续出队逻辑正确。

  5. 取出元素
    从 first 节点中取出要弹出的元素 x

  6. 清空元素引用
    将 first.item 置为 null,帮助 GC 回收元素对象,避免内存泄漏。

  7. 返回结果
    返回弹出的元素 x,完成出队操作。

加锁分析

加锁设计(高明之处)

  • 单锁 vs 双锁
    • 单锁:同一时刻最多允许一个线程(生产者 / 消费者二选一 )执行操作。
    • 双锁(putLock 、takeLock ):同一时刻允许一个生产者 + 一个消费者同时执行,消费者线程间、生产者线程间仍串行。

线程安全分析(按节点总数分类)

  1. 节点总数 > 2(含 dummy 节点)

    • putLock 保证 last 节点线程安全(入队操作 )。
    • takeLock 保证 head 节点线程安全(出队操作 )。
    • 两把锁隔离入队、出队操作,无竞争。
  2. 节点总数 = 2(1 个 dummy 节点 + 1 个正常节点)
    仍用两把锁锁定不同对象(lasthead 相关 ),无竞争。

  3. 节点总数 = 1(仅 dummy 节点)
    take 线程因队列为空,被 notEmpty 条件阻塞,有竞争时会阻塞等待。

// 用于 put(阻塞)、offer(非阻塞) 的锁,控制入队操作
private final ReentrantLock putLock = new ReentrantLock(); 
// 用于 take(阻塞)、poll(非阻塞) 的锁,控制出队操作
private final ReentrantLock takeLock = new ReentrantLock(); 

put操作

public void put(E e) throws InterruptedException {// 1. 校验元素非空if (e == null) throw new NullPointerException();int c = -1; // 记录入队前的元素数量Node<E> node = new Node<E>(e); // 创建新节点final ReentrantLock putLock = this.putLock; // 生产者锁final AtomicInteger count = this.count; // 队列元素数量(原子类,保证线程安全)// 2. 加锁(可中断锁)putLock.lockInterruptibly();try {// 3. 队列满时等待(循环检测,防止虚假唤醒)while (count.get() == capacity) {// 等待 notFull 条件(队列非满时被唤醒)notFull.await(); }// 4. 入队操作enqueue(node); // 将新节点加入队列尾部c = count.getAndIncrement(); // 元素数量 +1(先获取旧值,再自增)// 5. 唤醒其他生产者(如果队列仍有空位)if (c + 1 < capacity) {// 唤醒等待 notFull 的生产者线程notFull.signal(); }} finally {// 6. 释放锁putLock.unlock();}// 7. 唤醒消费者(如果队列从空变为非空)if (c == 0) {// 唤醒等待 notEmpty 的消费者线程notEmpty.signal(); }
}// 辅助方法:入队操作(私有方法,保证线程安全)
private void enqueue(Node<E> node) {// 队列的 last 节点指向新节点,完成入队last = last.next = node; 
}
  1. 校验元素
    元素为 null 时抛出异常。

  2. 初始化与加锁
    创建节点,获取生产者锁,可中断加锁。

  3. 队列满时等待
    队列满则循环等待 notFull 条件,释放锁并阻塞。

  4. 入队与计数
    将节点加入队列尾部,原子递增元素数量。

  5. 唤醒生产者
    队列仍有空位时,唤醒其他等待的生产者。(自己唤醒自己)

  6. 释放锁
    保证锁释放,避免死锁。

  7. 唤醒消费者
    队列入队前为空时,唤醒等待的消费者。

take操作

public E take() throws InterruptedException {E x; // 存储出队的元素int c = -1; // 记录出队前的元素数量final AtomicInteger count = this.count; // 队列元素数量(原子类,保证线程安全)final ReentrantLock takeLock = this.takeLock; // 消费者锁// 1. 加锁(可中断锁)takeLock.lockInterruptibly();try {// 2. 队列空时等待(循环检测,防止虚假唤醒)while (count.get() == 0) {// 等待 notEmpty 条件(队列非空时被唤醒)notEmpty.await(); }// 3. 出队操作x = dequeue(); // 从队列头部取出元素c = count.getAndDecrement(); // 元素数量 -1(先获取旧值,再自减)// 4. 唤醒其他消费者(如果队列仍有元素)if (c > 1) {// 唤醒等待 notEmpty 的消费者线程notEmpty.signal(); }} finally {// 5. 释放锁takeLock.unlock();}// 6. 唤醒生产者(如果队列从满变为非满)if (c == capacity) {// 唤醒等待 notFull 的生产者线程signalNotFull(); }return x;
}
  1. 加锁
    消费者线程加锁,支持中断。

  2. 队列空时等待
    队列空则循环等待 notEmpty 条件,释放锁并阻塞。

  3. 出队与计数
    从队列头部取出元素,原子递减元素数量。

  4. 唤醒消费者
    队列仍有元素时,唤醒其他等待的消费者。

  5. 释放锁
    保证锁释放,避免死锁。

  6. 唤醒生产者
    队列出队前满时,唤醒等待的生产者。

  7. 返回结果
    返回出队的元素。

LinkedBlockingQueue VS ArrayBlockingQueue

LinkedBlockingQueueArrayBlockingQueue
有界性支持支持有界(默认容量为 Integer.MAX_VALUE,可手动指定有界)强制有界(初始化必须指定容量,且不可动态扩容)
底层数据结构链表(Node 节点构成链表)数组(提前初始化固定大小的 Object 数组)
初始化特性懒惰初始化(首次入队时创建节点,无需提前分配内存)提前初始化(创建时需指定容量,数组直接分配内存)
节点创建时机每次入队(put/offer)时动态创建 Node初始化时提前创建数组元素(逻辑上的 “节点” 是数组槽位)
锁机制双锁分离(putLock 控制入队,takeLock 控制出队)单锁(ReentrantLock 同时控制入队和出队)
并发性能(典型场景)生产 / 消费可并行(双锁减少竞争),高并发下优势明显生产 / 消费互斥(单锁竞争激烈),高并发下性能受限
内存开销动态节点导致额外内存(Node 对象头、指针等)数组连续存储,内存更紧凑(无额外指针开销)
扩容能力理论上可无限扩容(受限于 Integer.MAX_VALUE不可扩容(容量固定,满了只能等待出队)
适用场景高并发生产消费、需动态扩容、内存不敏感场景低并发、容量固定、内存敏感场景

ConcurrentLinkedQueue

CopyOnWriteArrayList

CopyOnWriteArraySet 是它的马甲
底层实现采用了写入时拷贝的思想,增删改操作会将底层数组拷贝一份,更改操作在新数组上执行,这时不影响其它线程的并发读,读写分离。
以新增为例:

public boolean add(E e) {synchronized (lock) {// 获取旧的数组Object[] es = getArray();int len = es.length;// 拷贝新的数组(这里是比较耗时的操作,但不影响其它读线程)es = Arrays.copyOf(es, len + 1);// 添加新元素es[len] = e;// 替换旧的数组setArray(es);return true;}
}

这里的源码版本是 Java 11,在 Java 1.8 中使用的是可重入锁而不是 synchronized

  • CopyOnWriteArraySet 等基于写时拷贝(Copy - On - Write)思想的类,其他读操作未加锁,适合 “读多写少” 的应用场景,以 forEach 方法为例进行展示。
  • 适用场景:读多写少场景,读操作无需加锁,能高效并发读取;写操作因拷贝数组相对耗时,适合低频写操作的场景。
public void forEach(Consumer<? super E> action) {Objects.requireNonNull(action);for (Object x : getArray()) {@SuppressWarnings("unchecked") E e = (E) x;action.accept(e);}
}

get弱一致性

时间点操作结果
1Thread-0 执行 getArray()拿到数组快照 array = [1, 2, 3]
2Thread-1 执行 getArray()拿到同一数组快照 array = [1, 2, 3]
3Thread-1 执行 remove(0) → 触发写时拷贝新建数组 arrayCopy = [2, 3],替换 array 为 arrayCopy
4Thread-0 执行 array[index]访问的是旧数组 [1, 2, 3] 的元素

写时拷贝的 “快照” 特性
读线程(Thread-0)在时间点 1 拿到的是数组的快照(旧数组 [1, 2, 3])。即使后续写线程(Thread-1)修改了数组(替换为 [2, 3]),读线程仍会访问自己持有的旧快照。
→ 读操作可能访问到 “过期数据”,这就是弱一致性的体现。

迭代器弱一致性

CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
list.add(1);
list.add(2);
list.add(3);// 1. 获取迭代器(此时迭代器持有数组快照:[1,2,3])
Iterator<Integer> iter = list.iterator(); // 2. 新线程修改集合(触发写时拷贝,数组变为 [2,3])
new Thread(() -> {list.remove(0); System.out.println(list); // 输出: [2, 3]
}).start();// 3. 主线程继续用迭代器遍历
sleep1s(); // 等待子线程执行完毕
while (iter.hasNext()) {System.out.println(iter.next()); // 输出: 1, 2, 3 
}
  1. 快照机制
    迭代器创建时,会拷贝当前数组的快照(如 [1,2,3]),后续集合的修改(如 remove)会生成新数组([2,3]),但迭代器仍持有旧快照。

  2. 无感知遍历
    迭代器遍历的是创建时的数组快照,不感知后续集合的修改 → 遍历结果与 “当前集合实际数据” 不一致。

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

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

相关文章

小杰python高级(six day)——pandas库

1.数据可视化用于绘制 DataFrame 数据图形&#xff0c;它允许用户直接从 DataFrame 创建各种类型的图表&#xff0c;而不需要使用其他绘图库&#xff08;底层实际上使用了 Matplotlib&#xff09;。&#xff08;1&#xff09;plotDataFrame.plot(*args, **kwargs)功能&#xff…

第十六届蓝桥杯青少组C++省赛[2025.8.9]第二部分编程题(1 、庆典队列)

参考程序&#xff1a;#include <iostream> using namespace std;int main() {int n, A;cin >> n >> A; // 输入&#xff1a;n 和 A&#xff0c;用空格隔开cout << n / A; // 整数相除&#xff0c;自动向下取整return 0; }

C++进阶:智能指针

目录1. RAII与智能指针2. C库中的智能指针2.1 智能指针auto_ptr2.2 智能指针unique_ptr2.3 智能指针shared_ptr3. shared_ptr的循环引用4. 智能指针的定值删除器1. RAII与智能指针 上一篇文章学习了异常相关的知识&#xff0c;其中遗留了一个异常安全相关的问题。那就是异常的抛…

Tkinter 实现按钮鼠标悬浮提示:两种方案(继承Frame与不继承)

在 Tkinter 桌面应用开发中&#xff0c;为按钮添加“鼠标悬浮提示”是提升用户体验的常用功能——无需点击&#xff0c;只需将鼠标挪到按钮上方&#xff0c;就能自动显示按钮功能说明。本文将详细介绍两种实现方案&#xff1a;不继承 Frame 类&#xff08;快速简洁版&#xff0…

20250814 最小生成树总结

引子 啊啊额&#xff0c;从一张图里抽出几条边&#xff0c;组成一棵树&#xff0c;无环n−1n-1n−1条边&#xff0c;就是生成树。那么边权和最小的生成树就叫最小生成树&#xff0c;最大生成树同理。 kruskal最小生成树 要求kruskal最小生成树&#xff0c;我们首先用结构体数组…

数据大集网:实体店获客引流的数字化引擎,解锁精准拓客新密码​

​在实体店面临流量焦虑、获客成本攀升的当下&#xff0c;实体店获客引流工具的重要性愈发凸显。如何在激烈的市场竞争中精准触达目标客户、构建可持续的客流增长模式&#xff1f;数据大集网凭借其创新的智能获客体系与全链路服务能力&#xff0c;正成为万千实体店突破增长瓶颈…

nginx --ssl证书生成mkcert

github https://github.com/FiloSottile/mkcert/releases网盘下载地址 https://pan.baidu.com/s/1XI0879pqu7HXZMnmQ9ztaw 提取码: 1111windows使用示例

守拙以致远:个人IP的长青之道|创客匠人

2025年被认为是AI应用全面爆发的一年。各种人工智能工具在写作、制图、剪辑等领域广泛使用&#xff0c;大大提升了个人和团队的工作效率。对于个人IP而言&#xff0c;这类工具的出现确实带来了新的机会&#xff0c;但也伴随着一种现象——一些人开始过度依赖甚至神化AI&#xf…

USB 3.0 LTSSM 状态机

USB2.0在电源供应后&#xff0c;通过Pull Up D-来决定枚举LS&#xff0c;Pull Up D有一个USB高速握手过程&#xff0c;来决定HS FS。USB3.0则会通过链路训练&#xff08;Link Training&#xff09;&#xff0c;来准备USB3.0通信。每当我们插上USB线的时候&#xff0c;对于3.0的…

MySQL窗口函数与PyMySQL以及SQL注入

MySQL窗口函数与PyMySQL实战指南&#xff1a;从基础到安全编程 引言 在数据处理和分析领域&#xff0c;MySQL作为最流行的关系型数据库之一&#xff0c;其窗口函数功能为数据分析提供了强大的支持。同时&#xff0c;Python作为数据分析的主要语言&#xff0c;通过PyMySQL库与My…

高级项目——基于FPGA的串行FIR滤波器

给大家安利一个 AI 学习神站&#xff01;在这个 AI 卷成红海的时代&#xff0c;甭管你是硬核开发者还是代码小白&#xff0c;啃透 AI 技能树都是刚需。这站牛逼之处在于&#xff1a;全程用 "变量名式" 幽默 生活化类比拆解 AI&#xff0c;从入门到入土&#xff08;啊…

JPrint免费的Web静默打印控件:PDF打印中文乱码异常解决方案

文章目录JPrint是什么&#xff1f;中文乱码&#xff08;Using fallback font xxx for xxxx&#xff09;1.字体嵌入2.客户机字体安装开源地址相关目录导航使用文档端口号修改代理使用场景打印服务切换中文乱码解决方案 JPrint是什么&#xff1f; JPrint是一个免费开源的可视化静…

MFT 在零售行业的实践案例与场景:加速文件集成与业务协作的高效方案

零售行业竞争激烈、数字化转型迭代迅速&#xff0c;业务对数据与档案的传输、处理和整合要求极高。无论是新品上市市场数据&#xff0c;还是供应链物流单据&#xff0c;集成方式不论是通过API或是档案传输, 对于传输的稳定性,安全性与性能, 都会直接影响决策效率与顾客体验。MF…

OSG+Qt —— 笔记1 - Qt窗口加载模型(附源码)

🔔 OSG/OsgEarth 相关技术、疑难杂症文章合集(掌握后可自封大侠 ⓿_⓿)(记得收藏,持续更新中…) OSG+Qt所用版本皆为: Vs2017+Qt5.12.4+Osg3.6.5+OsgQt(master) 效果 代码(需将cow.osg、reflect.rgb拷贝至工程目录下) OsgForQt.ui main.cpp

开源安全云盘存储:Hoodik 实现端到端数据加密,Docker快速搭建

以下是对 Hoodik 的简单介绍&#xff1a; Hoodik 是一个使用 Rust 和 Vue 开发的轻量级自托管安全云存储解决方案采用了非对称RSA密钥对和AES混合加密策略&#xff0c;从文件存储加密到数据链路加密&#xff0c;全程保证数据安全支持Docker一键私有部署&#xff0c;数据和服务…

[C++] Git 使用教程(从入门到常用操作)

1. Git 简介 Git 是一款分布式版本控制系统&#xff0c;用来跟踪文件变化、协作开发、管理项目版本。 它是开源的&#xff0c;由 Linus Torvalds 在 2005 年开发&#xff0c;广泛用于开源与企业项目中。 2. 安装 Git Windows 前往 Git 官网 下载并安装。 安装时建议勾选 Git…

实盘回测一体的期货策略开发:tqsdk获取历史数据并回测,附python代码

原创内容第969篇&#xff0c;专注AGI&#xff0c;AI量化投资、个人成长与财富自由。 星球好多同学希望说说实盘&#xff0c;我们就从实盘开始吧。 我们选择tqsdk给大家讲解&#xff0c;tqsdk支持免费注册&#xff0c;使用模拟账户&#xff0c;历史和实时数据&#xff0c;方便…

大模型推理框架vLLM 中的Prompt缓存实现原理

背景&#xff1a;为什么需要Prompt缓存模块&#xff1f;在大模型问答多轮对话应用场景中&#xff0c;不同请求的 Prompt 往往有相同的前缀&#xff0c;比如&#xff1a;第一次问答&#xff1a;你是一名专业的电子产品客服&#xff0c;负责回答客户关于手机产品的咨询。请根据以…

Python之Django使用技巧(附视频教程)

概述 Django 是一个高级的 Python Web 框架&#xff0c;遵循 “batteries-included”&#xff08;内置电池&#xff09;理念&#xff0c;提供了构建 Web 应用所需的大部分组件&#xff0c;让开发者可以专注于业务逻辑而不是底层细节。视频教程&#xff1a;https://pan.quark.cn…

sqli-labs通关笔记-第44关 POST字符型堆叠注入(单引号闭合 手工注入+脚本注入3种方法)

目录 一、堆叠注入 二、源码分析 1、代码审计 2、SQL注入安全性分析 三、堆叠手注法 1、进入靶场 2、正确用户名密码登录 3、堆叠注入 4、查看数据库 四、联合手注法 1、获取列数 2、确认回显位 3、获取数据库名 4、获取表名 5、获取列名 6、获取字段 7、总结…