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;

相关推荐
冉冰学姐4 小时前
基于ssm的技能比赛报名管理系统29817vn0(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
java·数据库·spring·ssm 框架应用
代码雕刻家6 小时前
3.5.Maven-依赖管理-依赖配置&依赖传递
java·maven
Cg136269159746 小时前
JS-对象-Dom案例
开发语言·前端·javascript
!chen6 小时前
MyBatis-plus拓展之字段类型处理器、自动填充和乐观锁
java·tomcat·mybatis
故事和你916 小时前
sdut-程序设计基础Ⅰ-实验五一维数组(8-13)
开发语言·数据结构·c++·算法·蓝桥杯·图论·类和对象
Jin、yz7 小时前
JAVA 八股
java·开发语言
va学弟7 小时前
Java 网络通信编程(6):视频通话
java·服务器·网络·音视频
我是唐青枫7 小时前
C#.NET Span 深入解析:零拷贝内存切片与高性能实战
开发语言·c#·.net
pjw198809037 小时前
Spring Framework 中文官方文档
java·后端·spring
lxh01137 小时前
数据流的中位数
开发语言·前端·javascript