java高性能无锁队列——MpscLinkedQueue

MpscLinkedQueue 是 Netty 所实现的一个高性能的**基于多生产者单消费者的无锁队列。**主要用于在多线程环境下进行高效的数据交换。其设计目标是支持多个生产者线程并发地入队操作,而只允许单个消费者线程执行出队操作。这种队列常见于

核心设计目标:MPSC 场景下的极致性能

适用场景:需要高吞吐、低延迟的场景,比如事件驱动架构、消息传递系统、异步任务处理等。

关键特性:

  1. 多生产者单消费者(MPSC):

多个线程可以安全地并发地向队列中插入元素(enqueue)。

只有一个线程可以从队列中取出元素(dequeue)。

  1. 无锁(Lock-Free):

使用原子操作(如CAS)而非传统锁机制来协调线程间的访问,避免了上下文切换和阻塞,提高了性能。

  1. 链表结构:

基于链表节点链接而成,可动态扩展,不受固定容量限制(除非受内存限制)。

  1. 高效率:

通过消除锁竞争和减小同步开销,实现高吞吐量和低延迟,特别适合高并发场景。

  1. 内存预分配/缓存友好:

某些实现会采用对象池或缓存行填充来优化性能,减少伪共享(false sharing)和GC压力。

现代 CPU 以 缓存行(Cache Line)​ 为单位缓存内存数据,常见大小为 64 字节。

当多个线程访问 同一缓存行内的不同变量​ 时,即使它们逻辑上无关,也会导致缓存同步冲突 ------ 这就是 伪共享(False Sharing)。

缓存行填充:通过在变量周围添加无意义的"填充字段",让每个易变变量独占一个缓存行。

实现原理

链表结构

java 复制代码
// netty-common/src/main/java/io/netty/util/internal/mpsc/MpscLinkedQueue.java
public class MpscLinkedQueue<E> extends AbstractQueue<E> implements Queue<E>, Iterable<E> {
    // 队列头节点(仅消费者可见)
    private volatile Node consumerNode;
    // 队列尾节点(生产者可见)
    private volatile Node producerNode;

    // 节点定义(内存对齐,防止伪共享)
    static class Node {
        static final Node[] CACHE_LINE_PADDING = new Node[128]; // 缓存行填充
        volatile Object value;    //存储实际的数据项
        volatile Node next;        //指向下一个节点的原子引用
        static final Node[] CACHE_LINE_PADDING2 = new Node[128];
    }

    // 构造函数
    public MpscLinkedQueue() {
        Node node = new Node();
        node.value = null;
        producerNode = consumerNode = node;
    }
}

入队操作

生产者线程执行以下步骤:

a. 创建新节点;

b. 获取当前尾节点(tail),尝试将 tail.next 设置为新节点(使用 CAS);

c. 如果成功,再尝试推进 tail 指针到新节点(这一步也可能由其他生产者完成);

d. 如果失败(说明有竞争),自旋重试或使用帮助机制(helping);

java 复制代码
public boolean offer(E e) {
    if (e == null) throw new NullPointerException();
    final Node node = new Node();
    node.value = e;

    Node prev = producerNode.get();
    Node curr;
    do {
        curr = prev.next.get();
        if (curr != null) {    //已经有别的线程成功地把节点链接到了 prev后面
            // 帮助消费者推进 producerNode(避免旧生产者节点滞留)
            prev = U.loadFence(prev.next.get());
            continue;
        }
        node.next.set(curr); // 原子设置新节点的 next
    } while (!prev.next.compareAndSet(curr, node));

    // 原子更新 producerNode(仅当 prev 是当前尾节点)
    U.putOrdered(producerNode, node);
    return true;
}

其中,U.loadFence()/ putOrdered():都是用于控制 内存可见性与指令重排序​ 的工具方法。

loadFence():插入一个读屏障,在此指令之前的 所有读操作(load)必须在这个 fence 之前完成,之后的读不能重排到前面。从而确保能看到一个 最新、正确的引用值。

这是一种 "helping" 机制:当前生产者线程帮助其他线程推进 producerNode,避免因为某个线程挂起导致整个队列卡住。

putOrdered():执行一个"宽松的写操作",不一定立即刷新到主存,但保证最终写入对其他线程可见。

出队操作

消费者线程执行以下步骤:

a. 检查 head 是否等于 tail(或者 head.next == null),如果是则队列为空;

b. 否则,从 head.next 节点取出 value;

c. 推进 head 指针到下一个节点(这通常是一种"懒惰"更新,即允许 head 滞后于实际消费点,依赖 GC 回收旧节点);

d. 返回 value;

相关推荐
数研小生2 小时前
Full Analysis of Taobao Item Detail API taobao.item.get
java·服务器·前端
2401_838472512 小时前
C++图形编程(OpenGL)
开发语言·c++·算法
Wang15302 小时前
Java编程基础与面向对象核心概念
java
毕设源码-郭学长2 小时前
【开题答辩全过程】以 康复管理系统为例,包含答辩的问题和答案
java
毅炼2 小时前
hot100打卡——day17
java·数据结构·算法·leetcode·深度优先
winfreedoms2 小时前
java-网络编程——黑马程序员学习笔记
java·网络·学习
开开心心就好3 小时前
键盘改键工具免安装,自定义键位屏蔽误触
java·网络·windows·随机森林·计算机外设·电脑·excel
IManiy3 小时前
总结之Temporal全局速率控制(二)第三方速率控制服务设计
java
雨季6663 小时前
Flutter 三端应用实战:OpenHarmony “极简手势轨迹球”——指尖与屏幕的诗意对话
开发语言·javascript·flutter