Java中的ConcurrentLinkedQueue详解

Java中的ConcurrentLinkedQueue详解

一、概述

ConcurrentLinkedQueue 是 Java 并发包(java.util.concurrent)中提供的无界非阻塞线程安全队列,基于单向链表实现,采用 CAS(Compare-and-Swap) 操作和 无锁算法 保证并发安全。其核心设计目标是高吞吐量低延迟,适用于高并发场景下的生产者-消费者模型。

关键特性

  1. 无界性:理论上容量无限,但受内存限制。
  2. 非阻塞:操作永不阻塞线程,失败立即返回(如 poll() 在队列为空时返回 null)。
  3. 无锁设计:通过 CAS 和自旋机制实现线程安全,避免传统锁的开销。
  4. FIFO 顺序:严格遵循先进先出原则。
  5. 弱一致性迭代器:遍历时可能看到部分更新,但不会抛出 ConcurrentModificationException

二、内部数据结构

1. 节点类 Node<E>

privatestaticclassNode<E>{volatileE item;// 存储元素volatileNode<E> next;// 指向下一个节点// CAS 操作方法booleancasItem(E cmp,E val){...}booleancasNext(Node<E> cmp,Node<E> val){...}}
  • volatile 修饰:保证多线程可见性。
  • CAS 操作:通过 Unsafe 类实现原子性更新。

2. 队列指针

  • head:指向队列头部(可能滞后于实际头节点)。
  • tail:指向队列尾部(可能滞后于实际尾节点)。
  • 哨兵节点:初始化时 headtail 均指向一个 item=null 的哨兵节点。

三、核心方法与实现原理

1. 入队操作(offer/add)

publicbooleanoffer(E e){checkNotNull(e);Node<E> newNode =newNode<>(e);for(Node<E> t = tail, p = t;;){Node<E> q = p.next;if(q ==null){// p 是尾节点if(p.casNext(null, newNode)){if(p != t)casTail(t, newNode);// 更新 tailreturntrue;}}elseif(p == q){// 自引用节点,重置 tail p =(t !=(t = tail))? t : head;}else{ p =(p != t && t !=(t = tail))? t : q;}}}
  • CAS 竞争:多个线程可能同时尝试插入,仅一个成功。
  • tail 滞后更新:仅在 p != t 时更新 tail,减少 CAS 操作频率。

2. 出队操作(poll)

publicEpoll(){ restartFromHead:for(;;){for(Node<E> h = head, p = h, q;;){E item = p.item;if(item !=null&& p.casItem(item,null)){if(p != h)updateHead(h, p);// 更新 headreturn item;}elseif((q = p.next)==null){updateHead(h, p);returnnull;}elseif(p == q)continue restartFromHead;else p = q;}}}
  • CAS 移除:将头节点的 item 设为 null,延迟物理删除。
  • head 更新:若 p != h,则更新 head 指针。

3. 其他方法

  • peek():获取头元素但不移除,逻辑与 poll() 类似,不修改 item
  • size():遍历链表统计元素数,非线程安全(高并发下结果可能不准确)。
  • remove(Object o):遍历链表移除首个匹配元素,返回是否成功。

四、无锁并发控制机制

1. CAS 操作

  • 原子性更新:通过 Unsafe.compareAndSwapObject 实现对 itemnext 的原子修改。
  • 自旋重试:CAS 失败时循环重试,而非阻塞线程。

2. 松弛不变量(Relaxed Invariants)

  • head 滞后:可能指向已删除节点,仅在必要时更新(如遍历时遇到自引用节点)。
  • tail 滞后:减少更新频率,提升吞吐量。

3. 自引用节点

  • 标记删除:出队后,原头节点的 next 指向自身,防止其他线程误用。
  • 垃圾回收:物理删除由 GC 处理,避免频繁内存操作。

五、适用场景

  1. 高并发生产者-消费者模型:如日志处理、实时事件分发。
  2. 低延迟系统:如高频交易、游戏服务器。
  3. 无界缓冲需求:需动态扩展队列长度的场景。
  4. 弱一致性要求:允许短暂的数据不一致(如遍历时)。

LinkedBlockingQueue 对比

特性ConcurrentLinkedQueueLinkedBlockingQueue
线程安全机制CAS 无锁锁(ReentrantLock)
容量限制无界可选有界/无界
阻塞操作支持 put()/take()
吞吐量中等
内存占用节点结构更轻量可能更高

六、最佳实践

  1. 避免频繁调用 size():高并发下性能差,建议通过外部计数器统计。
  2. 合理预估容量:虽无界,但内存耗尽可能引发 OOM。
  3. 结合其他同步机制:如需精确控制,可与 SemaphoreCountDownLatch 联用。
  4. 弱一致性遍历:接受遍历时可能遗漏新元素,适用于非强一致性场景。

七、源码设计细节

  1. 哨兵节点:初始化时 headtail 指向同一个哨兵节点,简化边界条件处理。
  2. 自引用节点:出队后原头节点 next=self,标记为待回收。
  3. CAS 优化:节点构造时使用 Unsafe.putObject 替代 volatile 写操作,减少内存屏障开销。

八、总结

ConcurrentLinkedQueue 是 Java 并发编程中高性能无锁队列的典范,通过 CAS 和松弛不变量设计,在保证线程安全的同时最大化吞吐量。适用于对延迟敏感、无需严格容量控制的场景,但需注意其无界特性可能带来的内存风险。理解其底层机制(如 CAS、自旋、自引用节点)有助于在实际工程中合理应用。


LinkedList详解-Deque接口链表实现方案
Vert.x 4 学习笔记-EventLoop的回调队列原理与机制详解

Read more

LeetCode 42接雨水全解:暴力超时→DP降维打击→双指针极限压缩空间→单调栈栈式凹槽定位,全景式解析算法优化路径

LeetCode 42接雨水全解:暴力超时→DP降维打击→双指针极限压缩空间→单调栈栈式凹槽定位,全景式解析算法优化路径

文章目录 * 本篇摘要 * LeetCode 42 接雨水 详解 * ① 暴力解法(多循环嵌套,卡超时,因此后续使用了两种基于暴力优化的方法) * ② 动态规划解法 * 核心思想 * 步骤(三步走) * 举例说明 * 代码实现思路 * ③ 双指针解法(优化对应的dp的空间复杂度变成O(1)) * 双指针优化思路 * ④单调栈解法 * 单调栈简介 * 核心特点 * 常见用途 * 左边最近比当前数大的数(用单调栈) * 步骤: * 示例: * 最终结果: * 单调栈一般模版 * 关键点 * 注意点 * 单调栈不同选型需求 * 优势 * 引入单调栈 * 本篇小结 本篇摘要 本篇围绕LeetCode 42“接雨水”展开,剖析四种解法:暴力法通过嵌套循环统计每柱接水量,易超时;动态规划预先记录左右最大值,将复杂度降至O(n);双指针边遍历边更新极值,空间优化至O(1

By Ne0inhk
❿⁄₁₁ ⟦ OSCP ⬖ 研记 ⟧ 密码攻击实践 ➱ NTLM哈希传递攻击

❿⁄₁₁ ⟦ OSCP ⬖ 研记 ⟧ 密码攻击实践 ➱ NTLM哈希传递攻击

郑重声明:本文所涉安全技术仅限用于合法研究与学习目的,严禁任何形式的非法利用。因不当使用所导致的一切法律与经济责任,本人概不负责。任何形式的转载均须明确标注原文出处,且不得用于商业目的。 🔋 点赞 | 能量注入 ❤️ 关注 | 信号锁定 🔔 收藏 | 数据归档 ⭐️ 评论 | 保持连接💬 🌌 立即前往 👉晖度丨安全视界🚀 ▶ 信息收集  ▶ 漏洞检测 ▶ 初始立足点  ▶ 权限提升 ▶ 横向移动 ➢ 密码攻击 ➢ NTLM哈希传递攻击🔥🔥🔥 ▶ 报告/分析 ▶ 教训/修复 目录 1.密码破解 1.1 破解Windows哈希实践 1.1.1 NTLM哈希传递攻击概述 1.1.1.1 什么是NTLM哈希传递? 1.1.1.2 攻击应用场景 1.1.

By Ne0inhk

【数学建模】(LeetCode 1227)小鸟回笼/飞机座位问题

题目 有 nnn 只小鸟,各有自己的笼子(编号 1,2,⋯ ,n1, 2, \cdots, n1,2,⋯,n)。第一天,第一只小鸟(编号 1)没有回到自己的笼子(笼 1),而是随机进了其它某个笼子。后续的小鸟每天回来时,如果自己的笼子空着就进自己的笼子,否则从剩下的空笼子中随机选一个。 问:最后一只回笼的小鸟回到自己笼子的概率是多少? 这个问题和经典的飞机座位问题等价(见下),但需要注意的时初始条件不同,下面的问题第一个人位置也是随机的(可能回到自己的位置),而上面小鸟回笼问题则是在没有回到自己的笼子情况下。 有nnn位乘客即将登机,飞机正好有nnn个座位。第一位乘客的票丢了,他随便选了一个座位坐下。剩下的乘客将会:如果他们自己的座位还空着,就坐到自己的座位上;当他们自己的座位被占用时,随机选择其他座位,问: 第nnn位乘客坐在自己的座位上的概率是多少? 解答 **解:**设当所有鸟回到笼子后鸟和笼子编号的映射为 f(n)=m

By Ne0inhk
【优选算法必刷100题】第031~32题(前缀和算法):连续数组、矩阵区域和

【优选算法必刷100题】第031~32题(前缀和算法):连续数组、矩阵区域和

🔥艾莉丝努力练剑:个人主页 ❄专栏传送门:《C语言》、《数据结构与算法》、C/C++干货分享&学习过程记录、Linux操作系统编程详解、笔试/面试常见算法:从基础到进阶 ⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平 🎬艾莉丝的简介: 🎬艾莉丝的算法专栏简介: 目录 031  连续数组 1.1  解法一:暴力解法 1.2  解法二:前缀和在哈希表中 1.3  算法实现 1.3.1  C++实现 1.3.2  Java实现 1.4  博主手记 032  矩阵区域和 2.1

By Ne0inhk