ringbuffer 通过预分配数组、原子序号操作、2的幂次长度位运算、事件复用和序号栅栏实现无锁高性能通信,依赖内存序与缓存局部性,适用于高吞吐低延迟场景,但要求端到端非阻塞且避免gc。

Java 并发编程中,用无锁环形队列(RingBuffer)实现极致性能通信,核心在于避免锁竞争、减少内存屏障开销、利用 CPU 缓存局部性,并配合生产者-消费者模型的单写单读(或有严格序控的多写/多读)模式。Disruptor 是最典型的工程实践,但理解其原理比直接套用更重要。
环形缓冲区的本质:预分配 + 下标原子操作
RingBuffer 不是动态扩容的队列,而是一块固定大小的数组(T[] buffer),所有元素在启动时就完成实例化。通信不靠对象引用入队出队,而是通过两个 long 类型的序号(cursor 表示已发布位置,sequence 表示已消费位置)做原子增减和比较。JVM 对 long 的 volatile 读写在 x86 上是单指令,无锁且高效。
- 数组长度必须是 2 的幂次(如 1024、4096),便于用位运算(& (size - 1))替代取模,避免除法开销
- 每个槽位(slot)不存业务对象,而是存可复用的事件对象(Event),通过 set() / get() 方法填充/读取字段,避免频繁 GC
- 生产者调用 next() 获取下一个可用序号,publish() 标记该序号为就绪;消费者通过 waitFor() 等待目标序号就绪后再 get()
无锁的关键:依赖内存序 + 序号栅栏(Sequence Barrier)
无锁 ≠ 无同步。RingBuffer 用 volatile long 序号 + 显式内存屏障(如 Unsafe.storeFence)保证可见性,再通过 SequenceBarrier 协调多个消费者之间的依赖关系。例如:B 消费者依赖 A 消费者处理完才能读某条数据,Barrier 就会检查 A 的 sequence 是否 ≥ 目标值。
- Disruptor 中的 WaitStrategy(如 BusySpinWaitStrategy、PhasedBackoffWaitStrategy)决定消费者如何等待,直接影响延迟与 CPU 占用比
- 单生产者场景下,next() 可用 Unsafe.compareAndSwapLong 实现纯无锁递增;多生产者则需额外协调(如使用 Sequencer 的 multiProducerNext)
- 避免伪共享(False Sharing):每个序号变量(如 cursor、gatingSequences)需用 @Contended 或手动填充(7 个 long 字段)隔离到独立缓存行
真正发挥性能的前提:避免阻塞路径与 GC 压力
再快的 RingBuffer,一旦业务逻辑里出现 synchronized、BlockingQueue.take()、new 对象或日志打印,整体吞吐就会断崖下跌。极致性能要求端到端“非阻塞”和“对象复用”。
- 事件处理器(EventHandler)内不能 sleep、wait、IO 阻塞,复杂逻辑应异步投递到线程池,RingBuffer 只做高速中转
- 所有事件对象生命周期由 RingBuffer 管理:onEvent() 回调中只修改字段,不 new、不 close、不抛受检异常
- 日志建议用异步 Appender(如 Log4j2 AsyncLogger)或 ring-buffer-based 日志框架,避免直接 slf4j.info() 打断流水线
适用场景与警惕边界
RingBuffer 不是万能队列。它适合高吞吐、低延迟、事件驱动、生产消费节奏相对稳定的系统,比如金融行情分发、实时风控、游戏状态同步。
- 不适合消息需要持久化、重试、死信队列等企业级能力的场景——那是 Kafka/RocketMQ 的职责
- 不适合消费者处理时间波动极大(如从 DB 查数据)的场景——会导致下游序号卡住,上游无法推进
- 调试困难:没有传统队列的 peek()/size(),需通过 cursor - min(sequence...) 估算积压,建议集成 JMX 或暴露监控指标
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











