Disruptor
准备工作
1、建立一个工厂Event类,用于创建Event类实例对象。
2、需要有一个监听事件类,用于处理数据(Event类)。
3、实例化Disruptor实例,配置一系列参数,编写Disruptor核心组件。
4、编写生产者组件,向Disruptor容器发送数据。
注意事项
Disruptor实际上就是一个生产消费者模型,支持一对一、一对多、多对一、多边形等模式的操作。
RingBuffer实际就是一个环形数组结构,使用时槽的数量是2的N次方更利于二进制的计算。
举例说明:
- RingBuffer 大小 = 8
- 当前生产者已经发布了第 11 个事件
- 那么你要找到序号 11 对应的数组元素
- 数组下标 = 序号 % RingBuffer长度 = 11 % 8 = 3
| 序号 | 数组下标(序号 % 8) |
| --- | ------------ |
| 0 | 0 |
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| 5 | 5 |
| 6 | 6 |
| 7 | 7 |
| 8 | 0 ✅ (覆盖) |
| 9 | 1 ✅ |
| 10 | 2 ✅ |
| 11 | 3 ✅ |
| ... | ... |
[0] [1] [2] [3] [4] [5] [6] [7]
↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
0 1 2 3 4 5 6 7
8 9 10 11 ...
当序号超出 RingBuffer 容量后,数据就会覆盖旧的槽位,所以 Disruptor 通过依赖 consumer 进度(sequence) 来判断是否可以覆盖(否则会丢数据)。
WaitStrategy
Disruptor 的核心之一就是其高性能的 等待策略(WaitStrategy)。这决定了消费者线程在等待新的事件时,是“忙等(busy spin)”、还是“阻塞(blocking)”或是“自适应”等。
策略名称 | 描述 | 优点 | 缺点 | 适用场景 |
---|---|---|---|---|
BlockingWaitStrategy | 使用 LockSupport.park() 等待 |
CPU 占用低 | 延迟稍高 | 普通系统、对延迟要求不高 |
SleepingWaitStrategy | 线程短暂 Thread.sleep(0) + yield() 等待 |
CPU 占用低 | 延迟中等,吞吐也中等 | 日志收集、指标采集 |
YieldingWaitStrategy(首选) | 自旋 + Thread.yield() 等待 |
延迟低 | CPU 高 | 低延迟系统,多核环境 |
BusySpinWaitStrategy | 紧凑的自旋循环 | 延迟最低 | 极耗 CPU | 对延迟极端敏感、核心独占系统 |
LiteBlockingWaitStrategy | 类似 BlockingWaitStrategy 但优化了 signalAll() 的问题 |
较低延迟 | 同样可能存在锁竞争 | 比 Blocking 稍快的场景 |
PhasedBackoffWaitStrategy | 多阶段等待(自旋 → yield → block) | 综合平衡 | 配置复杂 | 延迟 + CPU双重平衡要求场景 |
YieldingWaitStrategy 的核心思路是:自旋检查(自忙等待)+ Thread.yield() 出让 CPU → 直到条件满足。
- 它不阻塞线程,而是频繁检查条件是否满足,如果不满足就让出 CPU 给其他线程,然后继续尝试 —— 从而兼顾延迟与 CPU 利用率。
1、初始 counter = 100: - 表示先自旋 100 次(CPU 忙等待)。
2、每次循环判断: - dependentSequence.get() < sequence 表示你要等待的事件还没准备好。
3、如果超出自旋次数(counter == 0): - 执行 Thread.yield():表示当前线程愿意让出 CPU 资源给其他线程。
4、重复上述过程直到条件满足。
Disruptor为什么快?
1、使用环形数组(RingBuffer)结构,避免内存频繁分配。
- 内部是一个固定大小的预分配数组(可视为缓存池)。
- 不使用链表、无对象创建/销毁->无GC压力。
- 元素在数组中可复用,降低了内存抖动。
2、避免锁机制:无锁设计(Lock-Free)
- 依赖于CAS原子操作。
- 多生产者/消费者通过Sequence控制访问,不加锁而是通过序列号协调。
- 多线程并发下性能仍非常稳定。
3、避免伪共享(Flase Sharing)
- 概念:伪共享指多个线程访问的变量虽然是不同的,但是它们在内存中紧挨着,恰好在同一个CPU缓存行(cache line)上,导致频繁的缓存失效和数据同步,从而严重影响性能。
比如:线程A修改Value1没回导致线程B缓存的Value2也会被标记为无效,迫使B从主内存重新加载,这就是 “伪共享”:虽然没有真实共享变量,却因为物理位置太近而导致缓存竞争。 - 对象字段间使用@sun.misc.Contended或手动填充缓存行,防止CPU缓存行冲突;
- 缓存友好型内存布局,极大提高L1/L2Cache命中率。
4、批量消费、减少上下文切换
5、等待策略WaitStrategy可调优
6、内存屏障和可见性有保障
- 使用volatile保证变量的可见性
- 避免不必要的内存屏障和指令重排,兼顾可见性和性能。