文档教程知识库【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址https://gitcode.com/doocs/source-code-hunter点击查看免费下载导读MpscLinkedQueueMulti-Producer Single-Consumer Linked Queue多生产者单消费者无锁链表队列是 Netty 内部实现的高性能无锁队列它被用作核心线程 NioEventLoop 中任务队列 taskQueue 的底层实现是支撑 Netty「串行无锁化设计」的关键基础设施之一。本文基于 Netty 4.1.6 源码从队列的初始化、头尾结点维护、offer() 无锁写入原理、remove() 不支持的原因以及伪共享规避等实现细节出发完整拆解这条队列「多生产者并发写入不加锁」的精妙设计帮助读者掌握 CAS 无锁队列的通用设计范式并理解其在 NioEventLoop 组件 与 HashedWheelTimer 调度 中的真实应用。MpscLinkedQueue 是什么在 Netty 核心中的核心成员 NioEventLoop 中任务队列的实现 taskQueue 便是 MpscLinkedQueue。MpscLinkedQueue 是 Netty 实现的一个基于多生产者单消费者的无锁队列针对 NioEventLoop 中任务队列的特点单消费者的场景在一开始就避免了从队列中取数据时加锁的必要最精妙的地方在于多生产者并发从队列中添加数据的时候也没有加锁达到了 Netty 所期望的高性能实现。这种设计并非偶然。从 Netty 的整体设计看Netty 高性能之道 中明确阐述了其「无锁化的串行设计」思想消息的处理尽可能在同一个线程内完成期间不进行线程切换从而避免多线程竞争和同步锁带来的性能损耗。NioEventLoop 正是这个串行化设计的执行者——它作为 Reactor 线程既要轮询处理 I/O 事件又要执行外部线程提交过来的普通任务与定时任务而所有任务都汇聚到 taskQueue 这一条队列上。外部业务线程生产者可以同时向 NioEventLoop 提交任务而消费任务的只有 NioEventLoop 自身这一个线程消费者这恰好构成一个典型的 MPSC 场景。上图展示了 NioEventLoop 通过 ChannelPipeline 中的 Handler 链对消息进行串行、无锁处理的工作模型——所有处理都发生在单个 I/O 线程内部不进行线程切换这正是 MpscLinkedQueue 得以无锁化的场景前提。MpscLinkedQueue 无锁并发线程安全写入原理尾结点的维护继承 AtomicReference 带来的原子能力首先MpscLinkedQueue 继承自AtomicReference也就是说 MpscLinkedQueue 通过继承 AtomicReference 的方式显式地维护了一个提供原子读写能力的变量value。而在 MpscLinkedQueue 中这个value是其内部维护的队列的尾结点。继承 AtomicReference 并非随意之举而是整个无锁方案的核心基础value字段对所有线程可见任何线程都能读到最新的尾结点引用getAndSet()/compareAndSet()等方法底层由 UNSAFE 的 CAS 指令保证原子性使得「把新节点设为尾结点」这一步可以在多线程并发下安全地完成。头结点的维护构造方法与哨兵节点MpscLinkedQueue() { MpscLinkedQueueNodeE tombstone new DefaultNodeE(null); headRef new FullyPaddedReferenceMpscLinkedQueueNodeE(); headRef.set(tombstone); setTail(tombstone); }在 MpscLinkedQueue 中维护着headRef头结点字段其队列内部节点的实现是MpscLinkedQueueNode。MpscLinkedQueueNode 是一个除了存放具体队列元素外只有next字段的节点也就是说 MpscLinkedQueue 的队列是单向的。构造过程的关键点先创建了一个 value 为null的DefaultNode即哨兵节点 tombstone它同时充当初始的头结点和尾结点headRef通过FullyPaddedReference封装其作用详见后文「伪共享的规避」一节在构造方法的最后通过setTail()方法将 MpscLinkedQueue 的尾结点字段value也设置为这个哨兵节点。头结点字段headRef的存在可以方便后续直接从头结点开始的队列操作消费者可以简单判断头尾节点是否相等来确认队列中是否有元素可以消费——初始状态下 head tail队列为空。无锁加入的核心offer() 与 replaceTail()Override SuppressWarnings(unchecked) public boolean offer(E value) { if (value null) { throw new NullPointerException(value); } final MpscLinkedQueueNodeE newTail; if (value instanceof MpscLinkedQueueNode) { newTail (MpscLinkedQueueNodeE) value; newTail.setNext(null); } else { newTail new DefaultNodeE(value); } MpscLinkedQueueNodeE oldTail replaceTail(newTail); oldTail.setNext(newTail); return true; } private MpscLinkedQueueNodeE replaceTail(MpscLinkedQueueNodeE node) { return getAndSet(node); }MpscLinkedQueue 的offer()方法很简短但恰恰就是整个队列元素加入的完整流程当元素被加入的时候空值校验如果加入的元素为null直接抛出NullPointerException节点封装判断加入的元素是否本身就是一个MpscLinkedQueueNode如果不是则用DefaultNode进行封装如果本身就是节点则先将它的next置为null确保新节点是干净独立的原子替换尾结点通过replaceTail()方法将当前被加入的节点通过 AtomicReference 所提供的getAndSet()方法设为队列的尾结点并返回先前的尾结点。这次操作由 UNSAFE 的 CAS 来保证操作的原子性链接旧尾结点将之前的尾结点的next指向新加入的节点本次加入宣告结束。整个操作的核心逻辑可以概括为「先让新节点在原子层面抢占尾结点位置再让旧尾结点指向新节点」。其精妙之处在于getAndSet()的原子性保证了每个新节点只会被一个生产者成功设为尾结点不会有多个生产者同时抢到同一个尾结点位置当多个生产者并发执行时线程 A 拿到的oldTail与线程 B 拿到的oldTail必然是不同的节点因为尾结点位置已被 CAS 唯一确定因此各自执行oldTail.setNext(newTail)时写的是不同节点的next字段互不干扰唯一可能的顺序差异是线程 B 已经把自己的节点设为尾结点但线程 A 还没来得及执行oldTail.setNext(newTail)即「链接」这一步晚于「抢占」这一步。这种情况下新节点的入队顺序可能不完全按照提交先后排列但由于是链表实现不会产生数据丢失或覆盖只是在不加锁的前提下队列顺序可能不会严格按照加入顺序——这在 NioEventLoop 的任务场景下并不是问题。从这里可以看出MpscLinkedQueue 利用了 AtomicReference 底层 UNSAFE 的能力通过 CAS 确保新设置进入value的节点必定能够和原先的节点达成一个且唯一的联系那么只需要自顶向下不断通过将这个联系变成引用一条队列便形成了。由于其实现是链表而不是数组也就没有涉及到资源的竞争。在这个前提下高并发的插入场景下每个新进入的新节点都将获取原尾位置value上的节点而自身将会被设置为其后驱节点重新放到尾结点位置上CAS 在不加锁的前提下保证了前后节点对应关系的唯一性完成了并发条件下不加锁的线程安全写入。为什么 MpscLinkedQueue 不支持 remove()在 MpscLinkedQueue 中是不支持remove()方法去从队列中移除任意一个元素的。原因很简单消费者和生产者是无锁的消费者可以通过比较队首和队尾元素是否一致来保证线程安全地从队首取数据但是remove()从队列中任意位置修改数据是线程不安全的主要体现在移除队尾元素可能会导致正在加入的新元素被丢弃。具体来说如果某个线程要从队列中移除「当前尾结点」而此时另一个生产者线程正在执行offer()生产者拿到的oldTail可能正是这个即将被移除的节点。当remove()将该节点的next断开后生产者后续执行oldTail.setNext(newTail)就会把新节点挂到一个已从队列断开的节点上造成新加入的元素「游离」在队列之外而被永久丢弃。因此为了保证 MPSC 场景下写入的绝对安全Netty 选择了不支持任意位置删除操作。MpscLinkedQueue 另外的实现细节伪共享的规避FullyPaddedReference 的填充MpscLinkedQueue 中的头节点被通过FullyPaddedReference封装。其内部前后分别填充 56 字节和 64 字节来进行填充以避免伪共享False Sharing导致的性能损耗使得头结点可以被高效地访问。伪共享的背景CPU 缓存以缓存行通常为 64 字节为单位进行读写如果多个线程频繁访问的变量恰好落在同一条缓存行上那么任何一个线程对其中一个变量的写操作都会导致其他线程的缓存行失效从而引发不必要的缓存同步开销。头结点headRef是消费者线程频繁读写的热点字段而value尾结点是生产者线程频繁读写的热点字段通过字节填充将它们隔离在不同缓存行中可以显著降低并发访问时的缓存竞争。值得注意的是FullyPaddedReference前后分别填充 56 字节和 64 字节——其中 56 字节加上引用自身的 8 字节正好凑满一条 64 字节缓存行这正是对现代 CPU 缓存行大小64 字节的精确适配。UNSAFE 偏移量赋值内存屏障的优化MpscLinkedQueue 在消费者消费数据后当将下一个节点设置为头结点的时候并不是直接进行赋值而是通过 UNSAFE 来根据偏移量赋值。这样做将略微提高性能主要是内存屏障 storestore 和 loadstore 之间的性能差异直接引用赋值会被编译器/JVM 插入必要的内存屏障以保证可见性通过 UNSAFE 按偏移量写入时可以精确控制内存屏障的使用规避掉一些非必要的屏障开销从而获得微小的性能提升。这类细节优化虽然单次收益不大但在 Netty 这种对性能锱铢必较的高性能框架中任何一个热点路径上的微小优化都会被放大。延伸Mpsc 队列在 Netty 中的典型应用场景一NioEventLoop 的任务队列NioEventLoop 作为 Netty 的 Reactor 线程其线程模型与职责可参考 EventLoop 组件源码解析其内部维护的 taskQueue 正是 MpscLinkedQueue。外部线程通过eventLoop.execute(Runnable task)向 NioEventLoop 提交任务时任务会被加入到这条队列中而 NioEventLoop 自身的单一线程在每次事件循环中批量取出任务执行。由于消费者唯一取出时天然不需要加锁由于入队使用 CAS多生产者并发提交也无需加锁——这正是 NioEventLoop 能承载「无锁化串行设计」的底层保证。场景二HashedWheelTimer 的任务转移队列除了 NioEventLoopNetty 的时间轮定时器 HashedWheelTimer 也使用了 Mpsc 队列。在 HashedWheelTimer schedule 分析 中可以确认「将任务对象添加到 mpsc 队列中mpsc 是多生产者单消费者的队列模型另外 mpscQueue 是无锁队列靠 CAS 实现的」并且「schedule 方法其实也用到 MpscQueue只是任务执行的时候会把任务从 PriorityQueue 转移到 MpscQueue 上」。时间轮的场景同样是典型的 MPSC 结构多个业务线程可以并发地向时间轮提交定时任务多生产者而时间轮的 Worker 线程负责消费任务单消费者。这里复用与 NioEventLoop 相同的无锁队列方案进一步印证了 MpscLinkedQueue 在 Netty 内部「高性能并发任务收口」场景下的通用价值。总结MpscLinkedQueue 用不到一百行的核心代码实现了一条兼顾「多生产者并发写入安全」与「单消费者高效取出」的无锁队列其设计要点可以归纳为设计要点实现手段解决的问题原子尾结点继承 AtomicReference用value字段保存尾结点为多生产者并发写入提供 CAS 基础哨兵节点构造时创建DefaultNode(null)同时作为头尾结点简化空队列判断头尾相等即为空无锁入队replaceTail()即getAndSet()抢占尾结点再链接旧尾结点并发写入不加锁且不丢数据无 remove()设计上禁止任意位置删除避免移除尾结点导致新入队元素被丢弃伪共享规避FullyPaddedReference前后填充 56/64 字节隔离头尾热点字段的缓存行冲突内存屏障优化UNSAFE 按偏移量赋值头结点减少非必要内存屏障微调性能对于想在自己的高并发组件中设计无锁队列的开发者而言MpscLinkedQueue 提供了一套可复制的范式用 CAS 原子地确立「前后节点唯一对应关系」用链表规避数组扩容的资源竞争用单消费者假设省掉出队锁再用缓存行填充与内存屏障优化把性能压榨到极致。结合 Netty 串行无锁化设计 的整体思路阅读便能理解这条队列在 Netty 高性能体系中所扮演的关键角色。赞分享文档教程知识库【免费下载链接】source-code-hunter 从源码层面剖析挖掘互联网行业主流技术的底层实现原理为广大开发者 “提升技术深度” 提供便利。目前开放 Spring 全家桶Mybatis、Netty、Dubbo 框架及 Redis、Tomcat 中间件等项目地址https://gitcode.com/doocs/source-code-hunter点击查看免费下载相关推荐Netty 源码剖析MpscLinkedQueue 无锁多生产者单消费者队列的实现原理Netty 源码剖析MpscLinkedQueue 无锁多生产者单消费者队列的实现原理 在 Netty 的核心组件 NioEventLoop 中任务队列 t文档教程技术博客知识库高性能C无锁队列ReaderWriterQueue高性能C无锁队列ReaderWriterQueue 项目介绍 ReaderWriterQueue 是一个为C设计的单生产者、单消费者SPSC无锁队并发编程终极指南Katana高性能消息队列如何实现异步任务处理终极指南Katana高性能消息队列如何实现异步任务处理 Katana作为下一代爬虫和蜘蛛框架其核心优势之一在于内置的高性能消息队列系统能够高效处理异步任务网络安全网页爬虫上一篇探秘开源游戏宝藏Awesome Unity Games下一篇zimfw 模块化架构揭秘从新手到专家的完整学习路径创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
VSCode 插件梳理,让 Codex 跑 Agent:Key 用 TaoToken /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/20 23:54:15
在浏览器侧边栏里直接问本地 AI:Page Assist 上手指南 在浏览器侧边栏里直接问本地 AI:Page Assist 上手指南 【免费下载链接】page-assist Use your locally running AI models to assist you in your web browsing 项目地址: https://gitcode.com/GitHub_Trending/pa/page-assist
正在读一篇很长的英文技术文章… · 2026/9/20 23:54:15
本地AI视频剪辑实战:Palmier Pro完整体验与避坑指南 在 Mac 上折腾视频剪辑这些年,我见过太多“新概念剪辑工具”,上来都说自己有多智能,结果百分之八十是套了一层 AI 壳,剪个片子还是得靠手。2024 年底我开始用 Palmier Pro,本来没抱太高期待,几周体验下来反… · 2026/9/21 0:39:26
个人特种证件查询网站被黑?这份速查手册救急 个人特种证件查询网站被黑?这份速查手册救急 网站突然弹窗、被挂马、甚至变成钓鱼页,后台却查不到异常日志?这种“网站被黑挂马不知道怎么办”的恐慌,是做过个人特种证件查询网站的开发者最熟悉的噩梦。别慌,这篇速查手册不讲虚的,直接拆解从代码层到服务器层的致命漏洞,给你一套能落地的防护方案。… · 2026/9/21 0:38:59
STM32驱动SPL06-001气压传感器:I2C通信与温度补偿实战 1. 项目缘起与整体设计思路1.1 为什么选SPL06-001这颗气压传感器先说选型这件事。市面上做气压测量的传感器不少,BMP280、BME280、MS5611、DPS310这些我都用过,最后在这个项目里定下SPL06-001,原因很实在:它的相对精度能到0.06 hP… · 2026/9/21 0:38:26
解决Codex频繁重连问题:本地代理配置与修复指南 这周被 Codex 整得有点上火——每次在终端里丢一个问题过去,它先沉默十几秒,日志里反复刷 connecting、reconnecting 的痕迹,循环整整五次之后才开始出字。一开始我以为是自己网络不稳,Wi-Fi 换了、热点也试了、网络环境也切换了&… · 2026/9/21 0:38:26
汽车制造业主数据治理架构与实施指南 1. 项目背景与核心价值解析汽车制造业作为典型的离散型制造行业,其数据管理复杂度远超一般行业。一辆普通乘用车包含2万多个零部件,涉及上下游数百家供应商,整个价值链产生的数据维度呈现几何级增长。德勤这份112页的解决方案正是针对这一行业… · 2026/9/21 0:38:26
本地部署大模型实战:从零打造个人AI知识库与Agent助手 作为一个把业余时间几乎都砸在折腾AI上的人,这些年我前前后后做过不少小项目,但真正让我觉得拿得出手、愿意一直维护下去的,是我最近完成的一个个人AI项目——一套跑在本地的“个人AI工作助手”。简单说,它不是一个只会聊天的玩具… · 2026/9/21 0:38:26
Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化 直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡… · 2026/9/21 0:02:39
Word表格编号全攻略:从列表编号到题注交叉引用 写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技… · 2026/9/21 0:02:39
从第一个站到第二个站:独立开发者的静态网站选型与落地实践 1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&… · 2026/9/20 0:00:41
agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程 agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程 【免费下载链接】agentic-awesome-skills AAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and … · 2026/9/21 0:00:18
gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析 gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析 【免费下载链接】gin-vue-admin 🚀ViteVue3Gin拥有AI辅助的基础开发平台,企业级业务AI开发解决方案,内置mcp辅助服务,内置skills管理,… · 2026/9/21 0:00:18