ArrayDeque 不宜直接用作 RingBuffer,因其底层虽为循环数组但无原子游标操作、非线程安全、会扩容破坏零分配特性,且 head/tail 不可外部访问;应选用 Disruptor、手写环形缓冲或 JCTools 队列替代。

ArrayDeque 在 Java 中底层是基于循环数组实现的,但它本身并不直接暴露环形缓冲区所需的游标(head/tail)或提供原子性的游标移位操作。若想把它“当作” RingBuffer 使用(比如用于无锁队列、日志缓冲等场景),必须手动管理头尾索引,并注意其内部结构与通用 RingBuffer 的差异。
理解 ArrayDeque 的内部索引逻辑
ArrayDeque 的底层数组是循环使用的,但它的 head 和 tail 并非标准 RingBuffer 中的“读指针/写指针”,而是按以下方式维护:
- head 指向队首元素(即 poll/remove 的位置),初始为 0;
- tail 指向下一个可插入位置(即 offer/add 的位置),初始为 0;
- 实际元素范围是 [head, tail)(左闭右开),长度 = (tail - head + elements.length) % elements.length;
- 数组容量始终是 2 的幂(扩容时翻倍),因此可用位运算优化模运算:index & (capacity - 1)。
模拟 RingBuffer 游标移位的关键计算
若你封装一个 RingBuffer 接口(如 nextWrite(), publish(), readNext()),需自行跟踪 writeIndex 和 readIndex,并映射到 ArrayDeque 的 head/tail。但注意:ArrayDeque 不允许外部修改 head/tail,所以不能直接复用其字段——只能借鉴其循环策略,另起一套索引管理:
- 设缓冲区容量为
capacity(建议 2 的幂),当前写位置writeIndex,读位置readIndex; - 写入后移位:
writeIndex = (writeIndex + 1) & (capacity - 1); - 读取后移位:
readIndex = (readIndex + 1) & (capacity - 1); - 可用空间判断:
available = (writeIndex - readIndex + capacity) & (capacity - 1)(注意:该式仅在 capacity 是 2 的幂时等价于模运算); - 避免与 ArrayDeque 自身的 add/poll 混用——否则 head/tail 会被内部逻辑篡改,导致索引错乱。
为什么不宜直接用 ArrayDeque 当 RingBuffer
ArrayDeque 的设计目标是通用双端队列,不是高性能、低延迟的 RingBuffer:
- 没有内置的生产者-消费者协调机制(如序号屏障、等待策略);
- 非线程安全的游标更新,多线程下需额外同步或使用 AtomicLong;
- 扩容会触发数组复制,破坏 RingBuffer “固定大小、零分配” 的核心优势;
- 其 head/tail 是 package-private 字段,无法安全访问或 CAS 更新。
更合适的替代方案
如果真需要 RingBuffer 语义,推荐:
- Disruptor 的 RingBuffer:工业级实现,支持多生产者/多消费者、序号协调、内存预分配;
- 手写简易 RingBuffer:用
Object[]+AtomicInteger readIndex/writeIndex,控制容量不变; - JCTools 的
MpmcGrowableArrayQueue或SpmcArrayQueue:高性能、无锁、有界/可扩容队列,比 ArrayDeque 更贴近 RingBuffer 行为。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











