Java中RingBuffer尾指针尾插通过AtomicInteger/AtomicLong实现CAS原子递增,配合2的幂容量下的位运算(tail & (capacity-1))完成环形索引,同时需校验tail ≤ head + capacity以避免覆盖未消费数据。

Java 中 CAS 在 RingBuffer 尾指针尾插的实现,核心是用 AtomicInteger 或 AtomicLong 保证多线程下 tail 指针的原子递增,同时配合模运算实现环形索引,避免锁竞争。
tail 指针必须用原子变量维护
RingBuffer 的尾指针(tail)代表下一个可写位置,多个生产者可能同时调用 tryPublish 或类似方法追加元素。若用普通 int,++ 操作非原子(读-改-写三步),会导致丢失更新。必须使用 AtomicInteger 或 AtomicLong,通过 incrementAndGet() 或 getAndIncrement() 实现线程安全的自增。
-
getAndIncrement()返回旧值,适合“先取索引再写入”的场景 -
incrementAndGet()返回新值,适合“先递增再校验容量”的流程 - 不建议用
compareAndSet()手动重试——除非有特殊语义(如条件写入),否则徒增复杂度
环形索引需用模运算或位运算映射到数组下标
缓冲区底层是固定长度数组(如 Object[] buffer),tail 值持续增长,但实际写入位置为 tail % capacity。为提升性能,通常要求 capacity 是 2 的幂,此时可用位运算 tail & (capacity - 1) 替代取模,效率更高且无符号溢出风险。
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 例如 capacity = 1024,则
mask = 1023,index = tail & mask - 注意:tail 本身可无限增长(long 类型更稳妥),仅索引做环形映射
- 不能直接用
tail % capacity处理负数——但 AtomicLong/tail 从 0 开始递增,不会为负,此处无问题
尾插前需检查剩余空间(避免覆盖未消费数据)
CAS 更新 tail 只解决并发写冲突,但不保证逻辑正确性。RingBuffer 是有界队列,必须确保 tail 不超过 head + capacity(即未被消费的数据量不能超容量)。典型做法是:先读 head,再计算可用空间,再 CAS tail。
立即学习“Java免费学习笔记(深入)”;
- 常见模式:
long tail = tailAtomic.get(); long head = headAtomic.get(); if (tail - head - 注意:head 可能被消费者并发更新,所以该检查是乐观的,失败则重试
- LMAX Disruptor 的
Sequencer使用两阶段协议(申请序列号 + 发布),进一步解耦申请与写入,提升吞吐
写入数据后需内存屏障确保可见性
CAS 操作自带 volatile 语义(读写屏障),但元素写入数组本身不是原子的。为防止指令重排序导致消费者看到部分构造的对象,应在 CAS tail 成功后、写入元素前插入写屏障(JVM 自动保障 volatile 写和 CAS 后的普通写不会重排到其前);更稳妥的做法是把元素数组声明为 volatile Object[],或使用 Unsafe.putObjectOrdered。
- 简单场景下,只要 tail CAS 成功后立即写入
buffer[index] = event,JVM 的 happens-before 规则已保证消费者在读到新 tail 后能看到该写入 - 若事件对象字段较多或含引用,建议用
lazySet或putOrdered写入数组,减少 store-store 屏障开销 - 避免在 tail CAS 前就写入数组——否则可能造成脏读

















