disruptor是“伪无锁”而非真正无锁,因其用cas和内存屏障替代阻塞锁,但底层仍依赖unsafe指令,特定场景可能退化为轻量级锁;ringbuffer序列号需64字节对齐防伪共享;事件发布须严格遵循申请→填充→提交三步;多消费者需依场景选workerpool或batcheventprocessor,并正确shutdown。

Disruptor 为什么不是“无锁”而是“伪无锁”
Disruptor 并没有完全消除锁,它只是把 synchronized 或 ReentrantLock 这类 JVM 层面的阻塞锁,换成了基于 CAS(Compare-And-Swap)和内存屏障的非阻塞同步原语。真正“无锁”(lock-free)指的是任意线程失败或暂停,都不影响其他线程继续推进 —— Disruptor 满足这点,但它内部仍依赖 Unsafe.compareAndSwapLong 等底层指令,而这些在某些 JVM 实现或 CPU 架构上可能退化为轻量级锁。
容易踩的坑:
- 误以为不用写锁就等于线程安全:生产者/消费者逻辑仍需严格遵循 RingBuffer 的发布-消费协议,比如漏调
publish()或重复调claim(),会导致数据丢失或越界 - 盲目替换
LinkedBlockingQueue:Disruptor 启动成本高、配置复杂,小吞吐或低频场景反而更慢
RingBuffer 的序列号怎么对齐才能避免伪共享
Disruptor 性能关键之一是让每个生产者/消费者的序列号变量(cursor、gatingSequences 等)独占 CPU 缓存行(通常 64 字节),否则多个核心频繁修改相邻变量会触发缓存行无效(false sharing),大幅拖慢 CAS 效率。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 用
@Contended注解(JDK 8+,需开启-XX:-RestrictContended)标记序列号字段 - 手动 padding:在序列号前后插入 7 个
long字段(共 56 字节),凑满 64 字节对齐 - 别信“自动填充”:Disruptor 3.x 默认不 padding,4.x 虽加了
Sequence类的 padding,但自定义EventProcessor中的序列引用仍需自己处理
如何正确发布一个事件到 RingBuffer
发布事件不是简单 set(),必须走 “申请 → 填充 → 提交” 三步,否则消费者可能读到未初始化的内存内容(尤其对象引用字段为 null)。
典型错误现象:
- 消费者拿到事件后,字段全是默认值(
0、null、false) - 偶发
NullPointerException,但调试时又不复现(典型的重排序 + 可见性问题)
正确流程:
- 调
ringBuffer.next()获取可用序号(阻塞直到有空位) - 用
ringBuffer.get(sequence)拿到事件实例,填充字段(此时对象已由 RingBuffer 预分配好) - 必须调
ringBuffer.publish(sequence),触发内存屏障,确保填充对消费者可见
注意:tryNext() 和 next(n) 有不同语义,批量发布时别混用。
多消费者场景下如何避免重复消费或漏消费
Disruptor 支持 WorkerPool(竞争消费)和 BatchEventProcessor(独占序列)两种模式,选错直接导致业务逻辑错乱。
使用场景判断:
- 日志落地、监控埋点等“只管发、不关心谁收”的场景 → 用
WorkerPool,多个消费者争抢同一批事件 - 订单状态机、事务补偿等强顺序、单实例处理场景 → 必须用
BatchEventProcessor,每个消费者绑定独立序列,靠SequenceBarrier协调依赖
关键参数:
-
WaitStrategy影响吞吐与延迟:生产环境慎用BusySpinWaitStrategy(吃满 CPU),优先选YieldingWaitStrategy - 多个
BatchEventProcessor共享同一个SequenceBarrier时,它们的消费进度会被自动取最小值,这是实现“所有消费者都处理完才推进”的基础机制
最常被忽略的一点:消费者 shutdown 必须调 halt() 而非直接中断线程,否则 Sequence 状态残留,重启后可能跳过部分事件。











