基于 :LMAX Disruptor 4.0.0(2023-09-29 发布,Java 11+);文内标注
⚠️ 4.0 变更处为与 3.4.4 的差异本文回答三个问题:① 为什么 Disruptor 比
ArrayBlockingQueue快一个数量级?② 这份性能是怎么用「环形数组 + 序号 + 内存屏障」实现的(逐行源码)?③ 落到业务里该怎么写(可直接运行的完整代码)?
目录
- 核心理论与定位
- [1.1 一句话定义](#1.1 一句话定义)
- [1.2 传统阻塞队列的三大性能杀手](#1.2 传统阻塞队列的三大性能杀手)
- [1.3 性能对比数据](#1.3 性能对比数据)
- [1.4 Disruptor 的解法总表](#1.4 Disruptor 的解法总表)
- [核心概念与数据结构 ★★★](#核心概念与数据结构 ★★★)
- [2.1 RingBuffer:环形数组](#2.1 RingBuffer:环形数组)
- [2.2 Sequence:序号](#2.2 Sequence:序号)
- [2.3 Sequencer:序号发生器](#2.3 Sequencer:序号发生器)
- [2.4 SequenceBarrier:序号屏障](#2.4 SequenceBarrier:序号屏障)
- [2.5 WaitStrategy:等待策略](#2.5 WaitStrategy:等待策略)
- [2.6 EventProcessor / EventHandler](#2.6 EventProcessor / EventHandler)
- [2.7 概念对照表](#2.7 概念对照表)
- [为什么快:七大核心机制 ★★★](#为什么快:七大核心机制 ★★★)
- [3.1 机制一:预分配环形数组(零 GC)](#3.1 机制一:预分配环形数组(零 GC))
- [3.2 机制二:序号驱动,用 CAS 替代锁](#3.2 机制二:序号驱动,用 CAS 替代锁)
- [3.3 机制三:缓存行填充,消除伪共享](#3.3 机制三:缓存行填充,消除伪共享)
- [3.4 机制四:批量消费,摊薄同步开销](#3.4 机制四:批量消费,摊薄同步开销)
- [3.5 机制五:单生产者免 CAS 快路径](#3.5 机制五:单生产者免 CAS 快路径)
- [3.6 机制六:序号屏障构成并行依赖图](#3.6 机制六:序号屏障构成并行依赖图)
- [3.7 机制七:全链路无垃圾](#3.7 机制七:全链路无垃圾)
- 整体流程与入口
- [源码逐步剖析 ★★★](#源码逐步剖析 ★★★)
- [5.1 步骤一:Disruptor 构造](#5.1 步骤一:Disruptor 构造)
- [5.2 步骤二:RingBuffer 与字段布局](#5.2 步骤二:RingBuffer 与字段布局)
- [5.3 步骤三:handleEventsWith 建立依赖图](#5.3 步骤三:handleEventsWith 建立依赖图)
- [5.4 步骤四:start 启动消费线程](#5.4 步骤四:start 启动消费线程)
- [5.5 步骤五:生产者 next() 申请槽位](#5.5 步骤五:生产者 next() 申请槽位)
- [5.6 步骤六:publish() 发布与内存屏障](#5.6 步骤六:publish() 发布与内存屏障)
- [5.7 步骤七:消费者 waitFor 与批量分发](#5.7 步骤七:消费者 waitFor 与批量分发)
- [5.8 步骤八:Sequence 推进的三种写语义](#5.8 步骤八:Sequence 推进的三种写语义)
- 关键类关系图
- 等待策略全解与选型
- [实战代码 ★★★](#实战代码 ★★★)
- [8.1 依赖引入](#8.1 依赖引入)
- [8.2 Hello World:最小可运行](#8.2 Hello World:最小可运行)
- [8.3 订单异步处理:菱形依赖图](#8.3 订单异步处理:菱形依赖图)
- [8.4 EventTranslator 三种写法](#8.4 EventTranslator 三种写法)
- [8.5 Spring Boot 集成](#8.5 Spring Boot 集成)
- [8.6 EventPoller:非阻塞拉取(Netty/推送场景)](#8.6 EventPoller:非阻塞拉取(Netty/推送场景))
- [8.7 压测代码](#8.7 压测代码)
- [8.8 4.0 新特性:批次回退 Rewind](#8.8 4.0 新特性:批次回退 Rewind)
- [常见坑与 FAQ](#常见坑与 FAQ)
- [9.1 十个必踩的坑](#9.1 十个必踩的坑)
- [9.2 FAQ](#9.2 FAQ)
- 参考资料
1. 核心理论与定位
1.1 一句话定义
一句话概括 :Disruptor 是 LMAX 交易所开源的单机进程内、无锁、有界的高性能线程间消息传递框架------它把"队列"从一个需要在头尾加锁的数据结构,改造成了"一个预先分配好的环形数组 + 一组不断递增的序号"。
它解决的不是"分布式消息"问题,而是单 JVM 内两个线程之间如何以纳秒级延迟、千万级 TPS 传递数据的问题。典型定位:
| 维度 | 说明 |
|---|---|
| 定位 | 进程内队列(In-Process Queue),不是 MQ |
| 模型 | 有界(RingBuffer 定长)、无锁(CAS + 内存屏障)、无垃圾(可零分配) |
| 支持模式 | 单/多生产者 × 多消费者;支持广播(同一事件多个消费者各消费一份)、并行、串行(流水线)、菱形依赖 |
| 延迟量级 | 优雅策略下 P99 约 百纳秒级;BusySpin 下平均延迟可低至几十纳秒 |
| 吞吐量级 | 单机 千万级 ops/s (对比 ArrayBlockingQueue 百万级) |
| 已知使用者 | LMAX 交易所核心撮合、Log4j2 AsyncLogger、Apache Storm、Dubbo(部分场景)、Netty 生态、HBase |
| 依赖 | 零第三方依赖,单 jar,源码量小(核心约 60 个类) |
#mermaid-svg-8x5YQFanXK50CVO3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8x5YQFanXK50CVO3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8x5YQFanXK50CVO3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8x5YQFanXK50CVO3 .error-icon{fill:#552222;}#mermaid-svg-8x5YQFanXK50CVO3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8x5YQFanXK50CVO3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8x5YQFanXK50CVO3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8x5YQFanXK50CVO3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8x5YQFanXK50CVO3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8x5YQFanXK50CVO3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8x5YQFanXK50CVO3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8x5YQFanXK50CVO3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8x5YQFanXK50CVO3 .marker.cross{stroke:#333333;}#mermaid-svg-8x5YQFanXK50CVO3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8x5YQFanXK50CVO3 p{margin:0;}#mermaid-svg-8x5YQFanXK50CVO3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8x5YQFanXK50CVO3 .cluster-label text{fill:#333;}#mermaid-svg-8x5YQFanXK50CVO3 .cluster-label span{color:#333;}#mermaid-svg-8x5YQFanXK50CVO3 .cluster-label span p{background-color:transparent;}#mermaid-svg-8x5YQFanXK50CVO3 .label text,#mermaid-svg-8x5YQFanXK50CVO3 span{fill:#333;color:#333;}#mermaid-svg-8x5YQFanXK50CVO3 .node rect,#mermaid-svg-8x5YQFanXK50CVO3 .node circle,#mermaid-svg-8x5YQFanXK50CVO3 .node ellipse,#mermaid-svg-8x5YQFanXK50CVO3 .node polygon,#mermaid-svg-8x5YQFanXK50CVO3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8x5YQFanXK50CVO3 .rough-node .label text,#mermaid-svg-8x5YQFanXK50CVO3 .node .label text,#mermaid-svg-8x5YQFanXK50CVO3 .image-shape .label,#mermaid-svg-8x5YQFanXK50CVO3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-8x5YQFanXK50CVO3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8x5YQFanXK50CVO3 .rough-node .label,#mermaid-svg-8x5YQFanXK50CVO3 .node .label,#mermaid-svg-8x5YQFanXK50CVO3 .image-shape .label,#mermaid-svg-8x5YQFanXK50CVO3 .icon-shape .label{text-align:center;}#mermaid-svg-8x5YQFanXK50CVO3 .node.clickable{cursor:pointer;}#mermaid-svg-8x5YQFanXK50CVO3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8x5YQFanXK50CVO3 .arrowheadPath{fill:#333333;}#mermaid-svg-8x5YQFanXK50CVO3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8x5YQFanXK50CVO3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8x5YQFanXK50CVO3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8x5YQFanXK50CVO3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8x5YQFanXK50CVO3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8x5YQFanXK50CVO3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8x5YQFanXK50CVO3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8x5YQFanXK50CVO3 .cluster text{fill:#333;}#mermaid-svg-8x5YQFanXK50CVO3 .cluster span{color:#333;}#mermaid-svg-8x5YQFanXK50CVO3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8x5YQFanXK50CVO3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8x5YQFanXK50CVO3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-8x5YQFanXK50CVO3 .icon-shape,#mermaid-svg-8x5YQFanXK50CVO3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8x5YQFanXK50CVO3 .icon-shape p,#mermaid-svg-8x5YQFanXK50CVO3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8x5YQFanXK50CVO3 .icon-shape .label rect,#mermaid-svg-8x5YQFanXK50CVO3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8x5YQFanXK50CVO3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8x5YQFanXK50CVO3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8x5YQFanXK50CVO3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 单个 JVM 进程
next() 抢槽位
publish() 发布
waitFor() 等待
生产者线程
Producer
RingBuffer
环形数组
消费者 1
BatchEventProcessor
消费者 2
BatchEventProcessor
消费者 3
BatchEventProcessor
全程无锁
仅 CAS + 内存屏障
关键认知 :Disruptor 不是一个"更好的队列",而是一种"共享内存式"的并发编程模型。 你要接受它的两个核心约束------① 事件对象是复用的 ,消费者不能长期持有引用;② 缓冲区是有界的,满了生产者必须自旋等待。接受了这两点,才能换来数量级的性能提升。
1.2 传统阻塞队列的三大性能杀手
以 ArrayBlockingQueue(下文简称 ABQ)为参照物,它是怎么慢下来的?
杀手一:锁(Lock)的竞争与上下文切换
ArrayBlockingQueue.put() / take() 内部用 ReentrantLock + 两个 Condition:
java
// java.util.concurrent.ArrayBlockingQueue 精简
public void put(E e) throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly(); // ← 竞争:可能挂起、可能让出 CPU
try {
while (count == items.length)
notFull.await(); // ← 队列满:线程进入等待队列,发生上下文切换
enqueue(e);
} finally {
lock.unlock();
}
}
问题在于:
| 问题 | 后果 |
|---|---|
每次操作都要 lock/unlock,即使无竞争也要执行 CAS + 内存屏障 |
基线开销高 |
有竞争时,未抢到锁的线程被 LockSupport.park() 挂起 |
上下文切换成本 ~ 1~10 μs |
唤醒后还要重新抢锁(AQS 队列) |
延迟抖动大,P99 差 |
| 锁保护的是"整个数组 + head/tail/count" | 生产者和消费者互相阻塞,即使操作的是不同槽位 |
关键认知 :锁的本质问题是它把"两个互不相干的线程"强行变成了"串行"。生产者写 index=5、消费者读 index=3,本来毫无冲突,但锁让它们必须排队。
杀手二:伪共享(False Sharing)
CPU 缓存以 缓存行(Cache Line,通常 64 字节) 为单位在 L1/L2/L3 之间搬运。如果两个变量位于同一缓存行,且被不同核心频繁写,就会发生"写失效风暴":
┌──────────── 同一个 64 字节缓存行 ────────────┐
│ head = 3 | tail = 7 | count = 4 │
└──────────────────────────────────────────────┘
▲ ▲
│ 写 │ 写
生产者核心0 消费者核心1
│ │
└──── 每次都让对方缓存失效,反复从 L3 重拉 ────┘
ABQ 的 head、tail、count 三个字段紧挨着声明------生产者和消费者各写各的,却共享同一个缓存行,性能凭空损失数倍。
杀手三:GC(对象分配与回收)
ABQ 每 put 一个元素就产生一个 Node/对象引用,高吞吐下:
每秒 1000 万个事件 → 每秒 1000 万个对象 → Young GC 频繁触发 → STW 停顿 → 延迟毛刺
在低延迟场景(如交易所撮合)中,一次 50ms 的 GC 停顿等于灾难。
1.3 性能对比数据
以下为 LMAX 官方技术论文(Disruptor: High performance alternative to bounded queues for exchanging data between concurrent threads)给出的基准数据,测试场景为 1 亿条消息通过队列传递:
Unicast(1 生产者 → 1 消费者)
| 实现 | 吞吐量 (ops/sec) | 平均延迟 | P99 延迟 | P99.99 延迟 |
|---|---|---|---|---|
ArrayBlockingQueue |
5,339,256 | 299 ms | 350 ms | 574 ms |
LinkedBlockingQueue |
3,448,795 | 546 ms | 704 ms | 1,008 ms |
| Disruptor(BlockingWaitStrategy) | 4,936,373 | 348 ms | 380 ms | 560 ms |
| Disruptor(BusySpinWaitStrategy) | 25,998,336 | 79 ms | 79 ms | 87 ms |
Pipelined(1 生产者 → 3 消费者流水线)
| 实现 | 吞吐量 (ops/sec) | 平均延迟 | P99 延迟 |
|---|---|---|---|
ArrayBlockingQueue |
5,539,506 | 695 ms | 1,056 ms |
| Disruptor(BusySpin) | 15,540,798 | 78 ms | 81 ms |
Sequencer(3 生产者 → 1 消费者)
| 实现 | 吞吐量 (ops/sec) |
|---|---|
ArrayBlockingQueue |
2,397,307 |
LinkedBlockingQueue |
1,125,040 |
| Disruptor(BusySpin) | 10,856,341 |
⚠️ 注意口径:上表是官方论文在特定硬件(2011 年前后的服务器)与特定测试参数下的结果,绝对值不能直接外推到你的机器。可传递的结论只有两条:
- 在
BusySpinWaitStrategy下,Disruptor 的吞吐是 ABQ 的 4~6 倍,延迟只有其 1/4;BlockingWaitStrategy下二者吞吐接近 ------因为此时瓶颈又回到"阻塞/唤醒"上了,说明等待策略的选择比框架本身更能决定性能。
1.4 Disruptor 的解法总表
| 传统队列的问题 | Disruptor 的对策 | 对应源码 |
|---|---|---|
| 加锁串行化 | 序号(Sequence)+ CAS,生产者只 CAS 一个 cursor,消费者只写自己的 sequence | MultiProducerSequencer#next |
| 头尾指针互相阻塞 | 消费者用独立的 Sequence并推进,与生产者解耦 | Sequence |
| 生产者不知道消费者进度 | Gating Sequence:生产前计算所有消费者"最小序号" | Util#getMinimumSequence |
| 边界判断难(环形数组) | availableBuffer + generation flag,用"轮次"判定槽位是否可覆盖 | MultiProducerSequencer#isAvailable |
| 伪共享 | 缓存行填充 (LhsPadding / RhsPadding / RingBufferPad) |
Sequence 类继承链 |
| 频繁 GC | 事件对象预分配 + 原地填充(EventFactory) | RingBufferFields#entries |
| 逐个传递的低效同步 | 批量消费 :一次 waitFor 拿一批,连续处理 |
BatchEventProcessor#run |
| 内存可见性靠锁 | 显式内存屏障 :4.0 用 VarHandle.releaseFence/acquireFence/fullFence(3.x 用 Unsafe.putOrderedLong / putLongVolatile) |
Sequence#set/setVolatile |
| 单生产者还要 CAS | ProducerType.SINGLE 走免 CAS 快路径 |
SingleProducerSequencer |
2. 核心概念与数据结构 ★★★
2.1 RingBuffer:环形数组
RingBuffer 是 Disruptor 的数据容器,本质是一个会"绕圈"的定长数组:
容量 8 的 RingBuffer,当前已发布到序号 11:
index: 0 1 2 3 4 5 6 7
┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
entries│ ev 8 │ ev 9 │ev 10 │ev 11 │ ev 4 │ ev 5 │ ev 6 │ ev 7 │
└──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘
▲ ▲
│ │
序号 8 的槽位 序号 11 的槽位(cursor 指向这里)
对应 8 & 0x7=0
序号 12 将落回 index 4(12 & 0x7 = 4),覆盖旧的序号 4
两个关键设计点:
-
容量必须是 2 的幂 。这样
sequence & (bufferSize - 1)就等价于取模,比%快得多(源码:indexMask = bufferSize - 1)。javapublic static int ceilingNextPowerOfTwo(final int x) { return 1 << (32 - Integer.numberOfLeadingZeros(x - 1)); } -
槽位里放的是"事件对象",而不是数据本身 。事件对象在构造时一次性创建 ,之后只做 原地 set 字段。这是"零 GC"的根基。
关键认知 :RingBuffer 是一个覆盖写(overwrite)的环形队列。它不扩容,只会覆盖旧数据。因此必须保证"生产者的写指针不能追上最慢的消费者" ------ 这个约束由 gating sequence 机制强制保证,也正是"有界"的全部含义。
2.2 Sequence:序号
Sequence 是整个框架的原子基石,它做了两件事:
- 用一个
volatile long保存序号; - 用 7 个
long字段在自己前后各填充 56 字节,把它单独隔离在一个(或两个)缓存行里,彻底消除伪共享。
java
// com.lmax.disruptor.Sequence ------ 4.0.0 的实际实现(继承链 + 三种写语义)
class LhsPadding
{
// 前填充 56 字节。4.0 用 56 个 byte 字段拼出来(3.x 是 7 个 long p1..p7,字节数相同)
protected byte p10, p11, p12, p13, p14, p15, p16, p17; // 8 字节
protected byte p20, p21, p22, p23, p24, p25, p26, p27; // 8 字节
protected byte p30, p31, p32, p33, p34, p35, p36, p37;
protected byte p40, p41, p42, p43, p44, p45, p46, p47;
protected byte p50, p51, p52, p53, p54, p55, p56, p57;
protected byte p60, p61, p62, p63, p64, p65, p66, p67;
protected byte p70, p71, p72, p73, p74, p75, p76, p77; // 合计 56 字节
}
class Value extends LhsPadding
{
// ⚠️ 4.0 里它不再是 volatile ------ 可见性改由 VarHandle + 显式栅栏精确控制
protected long value;
}
class RhsPadding extends Value
{
// 后填充同样是 56 个 byte(p90..p97 / p100..p107 / ... / p150..p157)
}
public class Sequence extends RhsPadding
{
static final long INITIAL_VALUE = -1L; // 初始 -1,所以第一个可用序号是 0
private static final VarHandle VALUE_FIELD; // 4.0 改用 VarHandle,摆脱 sun.misc.Unsafe
static
{
try
{
VALUE_FIELD = MethodHandles.lookup()
.findVarHandle(Sequence.class, "value", long.class);
}
catch (final Exception e)
{
throw new RuntimeException(e);
}
}
public Sequence()
{
this(INITIAL_VALUE);
}
public Sequence(final long initialValue)
{
// 3.x 是 UNSAFE.putOrderedLong(...);4.0 等价写法:release 栅栏 + 普通写
VarHandle.releaseFence();
this.value = initialValue;
}
public long get()
{
final long v = this.value;
VarHandle.acquireFence(); // 等价于 3.x 的 volatile 读
return v;
}
public void set(final long value)
{
// lazySet 语义:release 栅栏 + 普通写(3.x:putOrderedLong)
// 生产者/消费者"推进进度"用这个,最便宜
VarHandle.releaseFence();
this.value = value;
}
public void setVolatile(final long value)
{
// volatile 写语义:release 栅栏 + 普通写 + full 栅栏(3.x:putLongVolatile)
// 需要让其它线程"立刻看到"时用它
VarHandle.releaseFence();
this.value = value;
VarHandle.fullFence();
}
public boolean compareAndSet(final long expectedValue, final long newValue)
{
return VALUE_FIELD.compareAndSet(this, expectedValue, newValue);
}
public long incrementAndGet()
{
return (long) VALUE_FIELD.getAndAdd(this, 1L) + 1L;
}
public long addAndGet(final long increment)
{
return (long) VALUE_FIELD.getAndAdd(this, increment) + increment;
}
}
💡 4.0 的一个容易被忽略的底层重写 :3.x 整个框架重度依赖
sun.misc.Unsafe;4.0 把Sequence改成了VarHandle+VarHandle.releaseFence()/acquireFence()/fullFence()显式栅栏 ,RingBufferFields也从UNSAFE.getObject(entries, REF_ARRAY_BASE + ...)换回了普通数组下标访问 。这是因为最低版本提到 Java 11 后,VarHandle 已经能覆盖原来 Unsafe 的全部能力,而显式栅栏比lazySet/volatile更可控。读 4.0 源码时不要再去找UNSAFE。
关键认知 :Sequence的三种写语义是整个框架性能的关键:
set()(3.x:UNSAFE.putOrderedLong;4.0:releaseFence+ 普通写)------ store-store 语义,不刷新 store buffer,快。用在"推进进度"这类不需要立即可见的地方。setVolatile()(3.x:putLongVolatile;4.0:releaseFence+ 写 +fullFence)------ 全屏障,立即对其他核心可见,慢。用在"发布数据、必须让别人看到"的地方。compareAndSet()------ 用于多生产者抢序号。什么时候用哪个,是读 Disruptor 源码时最值得反复琢磨的地方。
2.3 Sequencer:序号发生器
Sequencer 是并发控制的核心,负责回答两个问题:
- 生产者问 :"我能写到哪个序号?(槽位空着吗?)" →
next(n) - 消费者问 :"我能读到哪个序号?(数据写好了吗?)" → 通过
SequenceBarrier
它有两个实现,对应两种并发模型:
| 实现类 | 适用场景 | next() 的并发手段 | 性能 |
|---|---|---|---|
SingleProducerSequencer |
只有一个线程调用 next() |
无 CAS,只用普通写 + 边界检查 | 最快(无原子操作) |
MultiProducerSequencer |
多线程并发调用 next() |
cursor.compareAndSet(current, next) 自旋 |
略慢(有 CAS 竞争) |
⚠️ 这是最容易用错的地方 :如果你声明了
ProducerType.SINGLE(默认值),但实际有多个线程调用ringBuffer.publishEvent(...),会静默地数据错乱 ------不报错,但事件会互相覆盖。反过来,如果你只有一个生产者却用了MULTI,只是白付了 CAS 的成本,不会出错。拿不准就用
MULTI。
两个实现内部还各自用填充类(SingleProducerSequencerPad / SingleProducerSequencerFields)把 nextValue、cachedValue 等热字段隔开。
2.4 SequenceBarrier:序号屏障
消费者不能直接读 RingBuffer------它必须先问屏障:"我要的序号 k 准备好了吗?"
java
public interface SequenceBarrier
{
/** 等待 sequence 变为可消费,返回实际可消费到的最大序号 */
long waitFor(long sequence) throws AlertException, InterruptedException, TimeoutException;
/** 获取当前游标(依赖的上游最小序号) */
long getCursor();
/** 是否被中断(用于停机) */
boolean isAlerted();
void alert();
void clearAlert();
void checkAlert() throws AlertException;
}
屏障做了三件事:
- 等待 :委托给
WaitStrategy,直到上游序号 ≥ 目标序号; - 依赖 :它依赖的不是"生产者游标"这一个值,而是所有前置消费者的最小序号 (通过
FixedSequenceGroup或单个Sequence); - 可见性 :处理多生产者下的"洞"------调用
sequencer.getHighestPublishedSequence()找到真正连续可用的上界。
关键认知 :
SequenceBarrier是 Disruptor 实现**依赖图(Dependency Graph)**的载体。你在 DSL 里写.then()、.after(),最终都变成"某个消费者的SequenceBarrier依赖另一组Sequence"。这就是它能做"流水线 / 菱形 / 并行分支"的原因。
2.5 WaitStrategy:等待策略
| 策略 | 实现方式 | CPU 占用 | 延迟 | 适用场景 |
|---|---|---|---|---|
BusySpinWaitStrategy |
死循环自旋 | 极高(占满一核) | 最低 | 线程数 < CPU 核数,追求极致低延迟 |
YieldingWaitStrategy |
自旋 + Thread.yield() |
高 | 很低 | 线程数 < CPU 核数,允许让出 CPU |
SleepingWaitStrategy |
自旋 → yield → parkNanos(100ns) |
中 | 中 | 默认推荐,异步日志等对延迟不极端敏感的场景 |
BlockingWaitStrategy |
ReentrantLock + Condition |
低 | 高(μs 级抖动) | CPU 资源紧张、吞吐优先 |
LiteBlockingWaitStrategy |
无锁 CAS + park/unpark |
低 | 中 | 3.4+ 新增,想省 CPU 又受不了重锁 |
TimeoutBlockingWaitStrategy |
带超时的阻塞(ReentrantLock) |
低 | 高 | 可用于停机等待;4.0 已改为 synchronized,GC-free |
LiteTimeoutBlockingWaitStrategy |
带超时的无锁阻塞 | 低 | 中 | 4.0 新增,LiteBlocking 的超时版 |
PhasedBackoffWaitStrategy |
分阶段组合(自旋 → yield → 阻塞) | 低 | 中 | 吞吐/延迟折中,可自定义各阶段 |
源码(SleepingWaitStrategy,注意其三段式降级):
java
public final class SleepingWaitStrategy implements WaitStrategy
{
private static final int DEFAULT_RETRIES = 200;
private static final long DEFAULT_SLEEP = 100; // 纳秒
private final int retries;
private final long sleepTimeNs;
@Override
public long waitFor(final long sequence, Sequence cursor, final Sequence dependentSequence,
final SequenceBarrier barrier) throws AlertException, InterruptedException
{
long availableSequence;
int counter = retries;
while ((availableSequence = dependentSequence.get()) < sequence)
{
counter = applyWaitMethod(barrier, counter);
if (Thread.currentThread().isInterrupted())
{
throw new InterruptedException();
}
}
return availableSequence;
}
private int applyWaitMethod(final SequenceBarrier barrier, int counter) throws AlertException
{
barrier.checkAlert(); // 每轮都检查是否需要停机
if (counter > 100)
{
--counter; // 阶段一:纯自旋
}
else if (counter > 0)
{
--counter;
Thread.onSpinWait(); // 阶段二:提示 CPU 进入低功耗自旋(JDK9+)
}
else
{
LockSupport.parkNanos(sleepTimeNs); // 阶段三:真正挂起 100 纳秒
}
return counter;
}
}
⚠️ 4.0 变更 :
ThreadHints.onSpinWait()已废弃,改用 JDK 9 的Thread.onSpinWait()。
2.6 EventProcessor / EventHandler
| 组件 | 角色 |
|---|---|
EventHandler<T> |
业务接口 ,你实现 onEvent(T event, long sequence, boolean endOfBatch) |
EventProcessor |
消费线程本体,内部循环 waitFor → 取事件 → 回调 EventHandler |
BatchEventProcessor |
唯一的(4.0 后)标准实现,批量消费 |
ExceptionHandler |
消费者抛异常时的兜底策略 |
LifecycleAware |
生命周期回调(onStart / onShutdown)⚠️ 4.0 已合并进 EventHandler 的默认方法 |
TimeoutHandler |
waitFor 超时时的回调(配合 TimeoutBlockingWaitStrategy) |
java
public interface EventHandler<T>
{
void onEvent(T event, long sequence, boolean endOfBatch) throws Exception;
}
endOfBatch 是批量消费的边界标记 :当 sequence == availableSequence 时为 true。它给了你一个天然的"刷盘/批量提交"时机------例如异步日志在 endOfBatch 时才 flush(),能显著降低 I/O 次数。
⚠️ 4.0 变更 :
BatchStartAware、LifecycleAware、SequenceReportingEventHandler三个接口被合并为EventHandler的 default 方法。4.0 的实际继承结构是:
java// 包级私有接口,用户代码不实现它,只实现 EventHandler interface EventHandlerBase<T> extends EventHandlerIdentity { void onEvent(T event, long sequence, boolean endOfBatch) throws Throwable; default void onBatchStart(long batchSize, long queueDepth) { } // ← 原 BatchStartAware default void onStart() { } // ← 原 LifecycleAware default void onShutdown() { } // ← 原 LifecycleAware default void onTimeout(long sequence) throws Exception { } } public interface EventHandler<T> extends EventHandlerBase<T> { void onEvent(T event, long sequence, boolean endOfBatch) throws Exception; default void setSequenceCallback(Sequence sequence) { } // ← 原 SequenceReportingEventHandler }所以 3.x 里
implements EventHandler<T>, LifecycleAware的写法,在 4.0 里要改成只implements EventHandler<T>并直接覆写onStart()/onShutdown()。另外onBatchStart的参数从"只有 batchSize(实际是 queueDepth)"改成了明确的(batchSize, queueDepth)两个参数。⚠️ 同时 4.0 移除了
WorkerPool/WorkProcessor及handleEventsWithWorkerPool(见 FAQ Q7)------需要"任务竞争式消费"(一条消息只被一个消费者处理)的场景,要自己在用户态实现。
2.7 概念对照表
| Disruptor 概念 | 类比 | 一句话职责 |
|---|---|---|
RingBuffer |
数组 / 队列容器 | 存事件对象,靠 indexMask 取模定位槽位 |
Event / EventFactory |
队列里的元素 | 预创建、可复用的数据载体 |
Sequence |
指针 | 一个被填充保护、可原子推进的 long |
Sequencer |
队列的"生产者端" | 分配序号、管理 gating、维护 availableBuffer |
SequenceBarrier |
队列的"消费者端" | 等待可消费序号、实现依赖关系 |
WaitStrategy |
Condition.await |
决定"等不到数据时怎么办" |
EventProcessor |
消费者线程 | 循环拉取并回调业务 Handler |
EventHandler |
业务监听器 | 写业务逻辑的地方 |
ProducerType |
--- | 声明单/多生产者,决定是否走 CAS |
Disruptor |
门面 / Builder | 装配 DSL、启动、停机 |
3. 为什么快:七大核心机制 ★★★
3.1 机制一:预分配环形数组(零 GC)
事件对象在 RingBuffer 创建时全部创建好,此后只做原地写入:
java
// RingBufferFields 构造:一次性填满数组(完整字段布局见 3.3 与 5.2)
private void fill(final EventFactory<E> eventFactory)
{
for (int i = 0; i < bufferSize; i++)
{
// 注意下标要加 BUFFER_PAD 偏移,元素放在数组的"有效区"
entries[BUFFER_PAD + i] = eventFactory.newInstance();
}
}
发布一个事件的完整过程没有任何对象分配:
java
// translator.translateTo(event, sequence) 只是往已存在的对象里 set 字段
ringBuffer.publishEvent((event, sequence) -> {
event.setOrderId(1001L); // 原地写字段,不 new
event.setAmount(99.5);
});
收益:稳态下 Young GC 次数趋近于 0,延迟毛刺消失。
3.2 机制二:序号驱动,用 CAS 替代锁
生产者不需要锁住整个数组,它只需要原子地抢占一个序号:
java
// MultiProducerSequencer.next ------ 核心就是一个 CAS 自旋
do
{
current = cursor.get();
next = current + n;
long wrapPoint = next - bufferSize; // 覆盖线:不能越过这里
long cachedGatingSequence = gatingSequenceCache.get();
if (wrapPoint > cachedGatingSequence || cachedGatingSequence > current)
{
long gatingSequence = Util.getMinimumSequence(gatingSequences, current);
if (wrapPoint > gatingSequence)
{
LockSupport.parkNanos(1); // 缓冲区满,自旋等待
continue;
}
gatingSequenceCache.set(gatingSequence);
}
else if (cursor.compareAndSet(current, next))
{
break; // 抢到了 [current+1, next] 这段序号
}
}
while (true);
为什么这比锁快?
| 维度 | ReentrantLock |
CAS |
|---|---|---|
| 无竞争路径 | CAS + 可能 park | 一次 CAS |
| 有竞争时 | 未抢到者被挂起(上下文切换 μs 级) | 自旋重试(ns 级) |
| 是否阻塞消费者 | 是(同一把锁) | 否 ,消费者只推进自己的 Sequence |
| 内存语义 | 由 AQS 保证 | 由 volatile 语义保证,粗粒度但足够 |
关键认知 :CAS 的优势不在于"更快",而在于竞争时不做上下文切换。当临界区极短(只是改一个 long)时,自旋重试远比挂起再唤醒划算。
3.3 机制三:缓存行填充,消除伪共享
Disruptor 对三类热点数据结构都做了填充:
(1)Sequence ------ 三明治填充
java
class LhsPadding { protected byte p10..p77; } // 56 字节前填充(3.x:7 个 long)
class Value extends LhsPadding { protected long value; } // 价值字段被夹在中间
class RhsPadding extends Value { protected byte p90..p157; }// 56 字节后填充
public class Sequence extends RhsPadding { ... }
(2)RingBuffer ------ RingBufferPad + 数组两端填充
java
abstract class RingBufferPad
{
protected byte p10, p11, ..., p77; // 同样是 56 字节
}
abstract class RingBufferFields<E> extends RingBufferPad
{
private static final int BUFFER_PAD = 32; // 4.0 直接写死 32;3.x 算作 128 / scale
private final long indexMask;
private final E[] entries; // 真正的存储
protected final int bufferSize;
protected final Sequencer sequencer;
RingBufferFields(final EventFactory<E> eventFactory, final Sequencer sequencer)
{
super(sequencer);
this.bufferSize = sequencer.getBufferSize();
if (bufferSize < 1) { throw new IllegalArgumentException("bufferSize must not be less than 1"); }
if (Integer.bitCount(bufferSize) != 1) { throw new IllegalArgumentException("bufferSize must be a power of 2"); }
this.indexMask = bufferSize - 1;
this.entries = new Object[bufferSize + 2 * BUFFER_PAD]; // ⚠️ 前后各留 32 个元素 = 128 字节
fill(eventFactory);
}
private void fill(final EventFactory<E> eventFactory)
{
for (int i = 0; i < bufferSize; i++)
{
entries[BUFFER_PAD + i] = eventFactory.newInstance(); // 预创建全部事件对象
}
}
// 4.0:普通数组下标访问(3.x 是 UNSAFE.getObject(entries, REF_ARRAY_BASE + ...))
protected final E elementAt(long sequence)
{
return entries[BUFFER_PAD + (int) (sequence & indexMask)];
}
}
注意 new Object[bufferSize + 2 * BUFFER_PAD]------数组两端各塞 128 字节的 padding (在指针压缩开启时 BUFFER_PAD = 32 个引用 × 4 字节 = 128 字节 = 2 个缓存行)。目的是防止"数组头对象(含 length 等元数据)"与"第 0 号元素"、"最后一个元素"与"相邻对象"共享缓存行;同时让"绕圈时的边界"少一些 corner case。注意访问时要统一加 BUFFER_PAD 偏移,这是读这段源码时最容易看漏的地方。
(3)Sequencer 的 nextValue / cachedValue
SingleProducerSequencerPad / SingleProducerSequencerFields 两层继承,把 nextValue、cachedValue 与父类字段隔开;SingleProducerSequencer 自身又用 p10..p77 做了第三层填充------为了两个热字段,Disruptor 在这里套了三层类。
⚠️ 注意 :如果你的 JDK 开启了
-XX:-RestrictContended,也可以用@sun.misc.Contended注解达到同样效果,但 Disruptor 选择手动填充以保证跨 JDK 兼容。
3.4 机制四:批量消费,摊薄同步开销
BatchEventProcessor 一次 waitFor 可以拿到一大批可消费的序号:
java
final long availableSequence = sequenceBarrier.waitFor(nextSequence);
while (nextSequence <= availableSequence) // ← 一次性处理一整批
{
event = dataProvider.get(nextSequence);
eventHandler.onEvent(event, nextSequence, nextSequence == availableSequence);
nextSequence++;
}
sequence.set(availableSequence); // ← 进度只写一次!
关键收益 :sequence.set() 是带内存屏障的写操作。如果每处理一条就写一次,1000 条就要 1000 次屏障;批量处理后只写一次。在高吞吐下这一项就能带来 2~3 倍提升。
3.5 机制五:单生产者免 CAS 快路径
SingleProducerSequencer 的 next() 完全没有原子操作 ------因为只有一个写入者,nextValue 是"自己私有"的:
java
public long next(int n)
{
long nextValue = this.nextValue;
long nextSequence = nextValue + n;
long wrapPoint = nextSequence - bufferSize;
long cachedGatingSequence = this.cachedValue;
if (wrapPoint > cachedGatingSequence || cachedGatingSequence > nextValue)
{
cursor.setVolatile(nextValue); // StoreLoad 屏障:发布进度给别人看
long minSequence;
while (wrapPoint > (minSequence = Util.getMinimumSequence(gatingSequences, nextValue)))
{
LockSupport.parkNanos(1L);
}
this.cachedValue = minSequence;
}
this.nextValue = nextSequence; // 纯本地写,无原子操作
return nextSequence;
}
再看 publish() 里一个极其精妙的次序:
java
public void publish(long sequence)
{
cursor.set(sequence); // ① lazySet:数值已写入 entries 后,再发布进度(store-store 有序)
waitStrategy.signalAllWhenBlocking(); // ② 唤醒阻塞中的消费者
}
① 必须写在"业务把数据填进 entries 之后"(这是由 RingBuffer.translateAndPublish 的 try/finally 保证的);② 的顺序也重要------先把 cursor 推上去,再发信号。
3.6 机制六:序号屏障构成并行依赖图
假设你要实现:订单事件 → 并行做「风控」和「审计」→ 两者都完成后 → 「入库」。
#mermaid-svg-1S9RoZtImvFxfBSo{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1S9RoZtImvFxfBSo .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1S9RoZtImvFxfBSo .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1S9RoZtImvFxfBSo .error-icon{fill:#552222;}#mermaid-svg-1S9RoZtImvFxfBSo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1S9RoZtImvFxfBSo .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1S9RoZtImvFxfBSo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1S9RoZtImvFxfBSo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1S9RoZtImvFxfBSo .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1S9RoZtImvFxfBSo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1S9RoZtImvFxfBSo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1S9RoZtImvFxfBSo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1S9RoZtImvFxfBSo .marker.cross{stroke:#333333;}#mermaid-svg-1S9RoZtImvFxfBSo svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1S9RoZtImvFxfBSo p{margin:0;}#mermaid-svg-1S9RoZtImvFxfBSo .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1S9RoZtImvFxfBSo .cluster-label text{fill:#333;}#mermaid-svg-1S9RoZtImvFxfBSo .cluster-label span{color:#333;}#mermaid-svg-1S9RoZtImvFxfBSo .cluster-label span p{background-color:transparent;}#mermaid-svg-1S9RoZtImvFxfBSo .label text,#mermaid-svg-1S9RoZtImvFxfBSo span{fill:#333;color:#333;}#mermaid-svg-1S9RoZtImvFxfBSo .node rect,#mermaid-svg-1S9RoZtImvFxfBSo .node circle,#mermaid-svg-1S9RoZtImvFxfBSo .node ellipse,#mermaid-svg-1S9RoZtImvFxfBSo .node polygon,#mermaid-svg-1S9RoZtImvFxfBSo .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1S9RoZtImvFxfBSo .rough-node .label text,#mermaid-svg-1S9RoZtImvFxfBSo .node .label text,#mermaid-svg-1S9RoZtImvFxfBSo .image-shape .label,#mermaid-svg-1S9RoZtImvFxfBSo .icon-shape .label{text-anchor:middle;}#mermaid-svg-1S9RoZtImvFxfBSo .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1S9RoZtImvFxfBSo .rough-node .label,#mermaid-svg-1S9RoZtImvFxfBSo .node .label,#mermaid-svg-1S9RoZtImvFxfBSo .image-shape .label,#mermaid-svg-1S9RoZtImvFxfBSo .icon-shape .label{text-align:center;}#mermaid-svg-1S9RoZtImvFxfBSo .node.clickable{cursor:pointer;}#mermaid-svg-1S9RoZtImvFxfBSo .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1S9RoZtImvFxfBSo .arrowheadPath{fill:#333333;}#mermaid-svg-1S9RoZtImvFxfBSo .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1S9RoZtImvFxfBSo .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1S9RoZtImvFxfBSo .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1S9RoZtImvFxfBSo .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1S9RoZtImvFxfBSo .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1S9RoZtImvFxfBSo .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1S9RoZtImvFxfBSo .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1S9RoZtImvFxfBSo .cluster text{fill:#333;}#mermaid-svg-1S9RoZtImvFxfBSo .cluster span{color:#333;}#mermaid-svg-1S9RoZtImvFxfBSo div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1S9RoZtImvFxfBSo .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1S9RoZtImvFxfBSo rect.text{fill:none;stroke-width:0;}#mermaid-svg-1S9RoZtImvFxfBSo .icon-shape,#mermaid-svg-1S9RoZtImvFxfBSo .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1S9RoZtImvFxfBSo .icon-shape p,#mermaid-svg-1S9RoZtImvFxfBSo .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1S9RoZtImvFxfBSo .icon-shape .label rect,#mermaid-svg-1S9RoZtImvFxfBSo .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1S9RoZtImvFxfBSo .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1S9RoZtImvFxfBSo .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1S9RoZtImvFxfBSo :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 生产者
RingBuffer
风控 Handler
审计 Handler
入库 Handler
对应 DSL:
java
disruptor.handleEventsWith(riskHandler, auditHandler) // 并行分支,各拿到一份事件
.then(storeHandler); // 汇聚:等两者都完成
源码层面:storeHandler 的 SequenceBarrier 的 dependentSequence 是 riskHandler 与 auditHandler 两个 Sequence 组成的 FixedSequenceGroup:
java
// AbstractSequencer
public SequenceBarrier newBarrier(Sequence... sequencesToTrack)
{
return new ProcessingSequenceBarrier(this, waitStrategy, cursor, sequencesToTrack);
}
// FixedSequenceGroup ------ 取组内最小序号,"都完成"即"最小值达标"
public final class FixedSequenceGroup extends Sequence
{
private final Sequence[] sequences;
@Override
public long get()
{
return Util.getMinimumSequence(sequences);
}
}
关键认知 :Disruptor 的"依赖编排"不是调度框架,而是"消费者各自阻塞在不同的序号条件上"。 没有任何中心调度器,所有协作都通过"读别人的 Sequence"完成。这是它能做到极低开销的原因。
3.7 机制七:全链路无垃圾
官方在 3.0 起就把"GC-free"作为硬指标,体现在:
| 位置 | 手段 |
|---|---|
| 事件对象 | 预分配 + 原地复用 |
Sequence 数组 |
gatingSequences 用数组而非 List 迭代(虽然 addGatingSequences 会 recreate,但只在构建期) |
waitFor 返回值 |
基本类型 long,不装箱 |
| 异常处理 | AlertException.INSTANCE 单例(故意不填 stack trace,避免每次停机都构造栈) |
TimeoutBlockingWaitStrategy |
4.0 用 synchronized 块替代 ReentrantLock,避免每次 await 产生 Condition 节点 |
java
// AlertException ------ 单例且无栈,专为"用它做控制流"而设计
public final class AlertException extends Exception
{
public static final AlertException INSTANCE = new AlertException();
private AlertException() { }
@Override
public Throwable fillInStackTrace() { return this; } // ← 关键:不做栈回溯
}
注意这个模式:用异常做控制流通常被诟病"慢",但这里通过覆写 fillInStackTrace() 消掉了唯一的开销。
4. 整体流程与入口

图 4-1 Disruptor 整体流程:构建期(单线程装配)→ 运行期(多线程并发)→ 停机期
入口清单(读源码建议按这个顺序):
| 顺序 | 类 | 看什么 |
|---|---|---|
| 1 | Disruptor |
门面、DSL、start/shutdown |
| 2 | RingBuffer / RingBufferFields |
数据布局、padding、translateAndPublish |
| 3 | AbstractSequencer |
gatingSequences 管理 |
| 4 | MultiProducerSequencer |
CAS + availableBuffer |
| 5 | SingleProducerSequencer |
免 CAS 快路径 |
| 6 | ProcessingSequenceBarrier |
waitFor + getHighestPublishedSequence |
| 7 | BatchEventProcessor |
消费循环 |
| 8 | *WaitStrategy |
等待策略 |
| 9 | Sequence |
内存语义 |
5. 源码逐步剖析 ★★★
5.1 步骤一:Disruptor 构造
java
// com.lmax.disruptor.dsl.Disruptor ------ 精简
public class Disruptor<T>
{
private final RingBuffer<T> ringBuffer;
private final Executor executor;
private final ConsumerRepository<T> consumerRepository = new ConsumerRepository<>();
private final AtomicBoolean started = new AtomicBoolean(false);
private ExceptionHandler<? super T> exceptionHandler = new FatalExceptionHandler();
public Disruptor(final EventFactory<T> eventFactory, final int ringBufferSize,
final ThreadFactory threadFactory)
{
this(RingBuffer.createMultiProducer(eventFactory, ringBufferSize), threadFactory);
}
public Disruptor(final EventFactory<T> eventFactory, final int ringBufferSize,
final ThreadFactory threadFactory, final ProducerType producerType,
final WaitStrategy waitStrategy)
{
this(RingBuffer.create(eventFactory, producerType, ringBufferSize, waitStrategy), threadFactory);
}
private Disruptor(final RingBuffer<T> ringBuffer, final ThreadFactory threadFactory)
{
this.ringBuffer = ringBuffer;
this.executor = Executors.newCachedThreadPool(threadFactory); // 每个消费者一个线程
}
}
⚠️ 4.0 变更 :接受 Executor 的构造器已被移除,统一改为 ThreadFactory。生产环境请务必传具名线程工厂 (见 8.5 的 NamedThreadFactory),否则线上 dump 时全是 pool-N-thread-M。
一个细节 :
Executors.newCachedThreadPool(threadFactory)------线程数无上限,每个 handler 一个线程。所以不要在一个 JVM 里建几十个 Disruptor 实例,线程会爆。
5.2 步骤二:RingBuffer 与字段布局
java
// RingBuffer.create
public static <E> RingBuffer<E> create(final EventFactory<E> factory, final ProducerType producerType,
final int bufferSize, final WaitStrategy waitStrategy)
{
switch (producerType)
{
case SINGLE:
return createSingleProducer(factory, bufferSize, waitStrategy);
case MULTI:
return createMultiProducer(factory, bufferSize, waitStrategy);
default:
throw new IllegalStateException(producerType.toString());
}
}
RingBufferFields 里的 8 个关键字段:
java
abstract class RingBufferFields<E> extends RingBufferPad
{
private static final int BUFFER_PAD = 32; // 数组两端各留 32 个引用(指针压缩下 = 128 字节)
private final long indexMask; // bufferSize - 1,取模用
private final E[] entries; // 真正的存储,长度 = bufferSize + 2 * BUFFER_PAD
protected final int bufferSize;
protected final Sequencer sequencer;
RingBufferFields(final EventFactory<E> eventFactory, final Sequencer sequencer)
{
super(sequencer);
this.bufferSize = sequencer.getBufferSize();
if (bufferSize < 1)
{
throw new IllegalArgumentException("bufferSize must not be less than 1");
}
if (Integer.bitCount(bufferSize) != 1)
{
throw new IllegalArgumentException("bufferSize must be a power of 2");
}
this.indexMask = bufferSize - 1;
this.entries = new Object[bufferSize + 2 * BUFFER_PAD]; // 字节码:new Object[bufferSize + 64]
fill(eventFactory);
}
/** 唯一的取值入口:注意 + BUFFER_PAD 偏移 */
protected final E elementAt(final long sequence)
{
return entries[BUFFER_PAD + (int) (sequence & indexMask)];
}
E get(final long sequence)
{
return elementAt(sequence);
}
}
⚠️ 两个读源码时的坑:
Integer.bitCount(bufferSize) != 1------ 容量不是 2 的幂会在构造时直接抛异常 。所以new Disruptor<>(OrderEvent::new, 1000, ...)是错的,要写 1024(或用Util.ceilingNextPowerOfTwo(1000))。- 取元素必须加
BUFFER_PAD偏移 ------ 数组实际长度是bufferSize + 64,序号s对应的下标是(s & indexMask) + 32。3.x 里这个偏移藏在REF_ARRAY_BASE里,4.0 直接写成+ BUFFER_PAD,反而更好读。
5.3 步骤三:handleEventsWith 建立依赖图
java
// Disruptor#handleEventsWith
@SafeVarargs
public final EventHandlerGroup<T> handleEventsWith(final EventHandler<? super T>... handlers)
{
return createEventProcessors(new Sequence[0], handlers);
}
EventHandlerGroup<T> createEventProcessors(final Sequence[] barrierSequences,
final EventHandler<? super T>[] eventHandlers)
{
checkNotStarted(); // 启动后不允许再改图
final Sequence[] processorSequences = new Sequence[eventHandlers.length];
// ① 建屏障:依赖 barrierSequences(首个 handler 时为空)
final SequenceBarrier barrier = ringBuffer.newBarrier(barrierSequences);
for (int i = 0, len = eventHandlers.length; i < len; i++)
{
final EventHandler<? super T> eventHandler = eventHandlers[i];
// ② 每个 handler 一个 BatchEventProcessor
final BatchEventProcessor<T> batchEventProcessor =
new BatchEventProcessor<>(ringBuffer, barrier, eventHandler);
if (exceptionHandler != null)
{
batchEventProcessor.setExceptionHandler(exceptionHandler);
}
// ③ 注册到 ConsumerRepository
consumerRepository.add(batchEventProcessor, eventHandler, barrier);
processorSequences[i] = batchEventProcessor.getSequence();
}
// ④ 把本组消费者的 Sequence 注册为"生产者的 gating"------关键!
updateGatingSequencesForNextInChain(barrierSequences, processorSequences);
return new EventHandlerGroup<>(this, consumerRepository, processorSequences);
}
private void updateGatingSequencesForNextInChain(final Sequence[] barrierSequences,
final Sequence[] processorSequences)
{
if (processorSequences.length > 0)
{
// 生产者之后会通过 Util.getMinimumSequence(gatingSequences) 来防止覆盖
ringBuffer.addGatingSequences(processorSequences);
// 本组已"接手",把上游从 gating 中摘掉(避免上游成为瓶颈)
for (final Sequence barrierSequence : barrierSequences)
{
ringBuffer.removeGatingSequence(barrierSequence);
}
consumerRepository.unMarkEventProcessorsAsEndOfChain(barrierSequences);
}
}
关键认知 :
updateGatingSequencesForNextInChain里"加本组、摘上游 "这一手非常重要。在菱形图A → B → C中,生产者只需要看B的进度就够了(B 又必须等 A),摘掉 A 可减少getMinimumSequence的遍历量,也避免 A 拖慢生产。
5.4 步骤四:start 启动消费线程
java
// Disruptor#start
public RingBuffer<T> start()
{
checkOnlyStartedOnce(); // AtomicBoolean CAS,二次调用抛异常
for (final ConsumerInfo consumerInfo : consumerRepository)
{
consumerInfo.start(executor); // executor.execute(batchEventProcessor)
}
return ringBuffer;
}
注意 start() 并没有"启动"生产者 ------生产者就是你自己调用 ringBuffer.publishEvent() 的线程。start() 只是把 N 个 BatchEventProcessor(都是 Runnable)提交到线程池。
5.5 步骤五:生产者 next() 申请槽位
MultiProducerSequencer.next() 已经在 3.2 展开,这里补充两个易被忽略的细节:
细节 A:gatingSequenceCache 是必需品,不是优化
java
if (wrapPoint > cachedGatingSequence || cachedGatingSequence > current)
{
// 只有"可能追上消费者"时,才去遍历所有 gating sequence
long gatingSequence = Util.getMinimumSequence(gatingSequences, current);
...
gatingSequenceCache.set(gatingSequence);
}
如果每轮 都遍历所有消费者序号,N 个消费者就是 N 次 volatile 读------成本高且造成缓存行争抢。用 cachedGatingSequence 做本地缓存后,绝大多数循环只需一次本地读。
细节 B:缓冲区满时用 LockSupport.parkNanos(1) 而非 WaitStrategy
java
if (wrapPoint > gatingSequence)
{
LockSupport.parkNanos(1); // ← 硬编码 1 纳秒,不委托给 WaitStrategy
continue;
}
源码里带着一句 // TODO, should we spin based on the wait strategy?。这是一个已知的粗糙设计 :即使消费者用了 BusySpinWaitStrategy,生产者满时也只能 parkNanos。理解这一点有助于解释"为什么我换了策略性能没变"。
实践推论 :如果你追求极致吞吐,应当保证 RingBuffer 永远不满(容量给足 + 消费者够快),这样生产者走的就是零阻塞的快路径。
5.6 步骤六:publish() 发布与内存屏障
RingBuffer 侧的发布入口:
java
// RingBuffer
public void publish(final long sequence)
{
sequencer.publish(sequence);
}
public <A> void publishEvent(final EventTranslatorOneArg<E, A> translator, final A arg)
{
final long sequence = sequencer.next();
translateAndPublish(translator, sequence, arg);
}
private <A> void translateAndPublish(final EventTranslatorOneArg<E, A> translator,
final long sequence, final A arg)
{
try
{
translator.translateTo(elementAt(sequence), sequence, arg); // ① 先写数据
}
finally
{
sequencer.publish(sequence); // ② 再发布序号(finally 保证)
}
}
⚠️ 注意
finally:即使业务在填充事件时抛异常,序号也必须发布出去 ,否则消费者会永远卡在这个序号上(这是"序号空洞"问题)。这个设计选择意味着:如果你的translateTo抛了异常,消费者会收到一个"字段可能只填了一半"的事件 。所以translateTo里只应做"赋值",不要做有副作用的业务调用。
MultiProducerSequencer.publish:
java
public void publish(final long sequence)
{
setAvailable(sequence); // ① 标记该槽位"当前轮次已就绪"
waitStrategy.signalAllWhenBlocking(); // ② 唤醒阻塞的消费者
}
private void setAvailable(final long sequence)
{
setAvailableBufferValue(calculateIndex(sequence), calculateAvailabilityFlag(sequence));
}
private static final VarHandle AVAILABLE_ARRAY = MethodHandles.arrayElementVarHandle(int[].class);
private void setAvailableBufferValue(int index, int flag)
{
// 4.0 写法(3.x:UNSAFE.putOrderedInt,lazySet 语义)
AVAILABLE_ARRAY.setRelease(availableBuffer, index, flag);
}
private int calculateAvailabilityFlag(final long sequence)
{
return (int) (sequence >>> indexShift); // 轮次 = sequence / bufferSize
}
private int calculateIndex(final long sequence)
{
return ((int) sequence) & indexMask; // index = sequence % bufferSize
}
这是多生产者下解决"序号空洞"的核心机制:
bufferSize = 8, indexShift = 3
生产者 A 抢到序号 12 → index = 12 & 7 = 4,flag = 12 >>> 3 = 1
生产者 B 抢到序号 13 → index = 13 & 7 = 5,flag = 1
若 B 先写完并 publish(13),则 availableBuffer[5] = 1
A 后 publish(12),availableBuffer[4] = 1
消费者问 isAvailable(12):availableBuffer[4] == 1 ? 是 → 可读
消费者问 isAvailable(13):availableBuffer[5] == 1 ? 是 → 可读
注意 flag 用"轮次"而非固定值,天然区分了"上一轮的旧数据":
序号 4 → index 4,flag = 4 >>> 3 = 0
序号 12 → index 4,flag = 1 ← 同一个槽位,flag 不同,不会误判
消费者端据此裁剪出连续可用的上界:
java
public long getHighestPublishedSequence(long lowerBound, long availableSequence)
{
for (long sequence = lowerBound; sequence <= availableSequence; sequence++)
{
if (!isAvailable(sequence))
{
return sequence - 1; // 遇到第一个空洞,就停在它前面
}
}
return availableSequence;
}
性能提示 :这个 for 循环在"生产者并发度高、发布顺序乱"时会多跑几圈。优化办法是让生产者尽量按序号顺序发布 (例如单生产者场景),或者用
ProducerType.SINGLE完全绕开------单生产者模式下getHighestPublishedSequence直接返回availableSequence,没有循环。
多生产者的 isAvailable 与 setAvailableBufferValue 是一对配对的内存序操作:
java
public boolean isAvailable(final long sequence)
{
final int index = calculateIndex(sequence);
final int flag = calculateAvailabilityFlag(sequence);
// 4.0:getAcquire(3.x:UNSAFE.getIntVolatile)
return (int) AVAILABLE_ARRAY.getAcquire(availableBuffer, index) == flag;
}
生产者侧 setRelease(release 语义)、消费者侧 getAcquire(acquire 语义)------这一对 release/acquire 保证了"生产者写入 entries 的数据"对读到 flag 的消费者必然可见。这就是"用一对精心挑选的内存序代替一把锁"的教科书例子。
5.7 步骤七:消费者 waitFor 与批量分发
java
// ProcessingSequenceBarrier
final class ProcessingSequenceBarrier implements SequenceBarrier
{
private final WaitStrategy waitStrategy;
private final Sequence dependentSequence;
private volatile boolean alerted = false;
private final Sequence cursorSequence;
private final Sequencer sequencer;
ProcessingSequenceBarrier(final Sequencer sequencer, final WaitStrategy waitStrategy,
final Sequence cursorSequence, final Sequence[] dependentSequences)
{
this.sequencer = sequencer;
this.waitStrategy = waitStrategy;
this.cursorSequence = cursorSequence;
if (0 == dependentSequences.length)
{
dependentSequence = cursorSequence; // 无前置:直接看生产者游标
}
else
{
dependentSequence = new FixedSequenceGroup(dependentSequences); // 有前置:取最小
}
}
@Override
public long waitFor(final long sequence)
throws AlertException, InterruptedException, TimeoutException
{
checkAlert(); // 停机检查
long availableSequence = waitStrategy.waitFor(sequence, cursorSequence,
dependentSequence, this);
if (availableSequence < sequence)
{
return availableSequence;
}
// 多生产者:裁剪到"连续可读"的上界
return sequencer.getHighestPublishedSequence(sequence, availableSequence);
}
@Override
public void alert()
{
alerted = true;
waitStrategy.signalAllWhenBlocking(); // 唤醒阻塞中的线程来响应停机
}
}
消费主循环(BatchEventProcessor.run,4.0 加入 batchSize / rewind 后有所增补,此处为核心骨架):
java
@Override
public void run()
{
if (!running.compareAndSet(false, true))
{
throw new IllegalStateException("Thread already running");
}
sequenceBarrier.clearAlert();
notifyStart(); // LifecycleAware#onStart
T event = null;
long nextSequence = sequence.get() + 1L; // 从自己上次的进度继续
try
{
while (true)
{
try
{
final long availableSequence = sequenceBarrier.waitFor(nextSequence);
while (nextSequence <= availableSequence)
{
event = dataProvider.get(nextSequence);
eventHandler.onEvent(event, nextSequence, nextSequence == availableSequence);
nextSequence++;
}
sequence.set(availableSequence); // 批量推进,只写一次
}
catch (final TimeoutException e)
{
notifyTimeout(sequence.get()); // TimeoutHandler 回调
}
catch (final AlertException ex)
{
if (!running.get())
{
break; // 正常停机路径
}
}
catch (final Throwable ex)
{
exceptionHandler.handleEventException(ex, nextSequence, event);
sequence.set(nextSequence); // 跳过错行的事件,避免卡死
nextSequence++;
}
}
}
finally
{
notifyShutdown(); // LifecycleAware#onShutdown
running.set(false);
}
}
⚠️ 重点看异常分支 :默认的
FatalExceptionHandler会重新抛出 RuntimeException,导致这个消费线程直接死掉 ------线程死了,RingBuffer 很快写满,整个链路 hang 住。生产环境必须自定义 ExceptionHandler(见 FAQ 9.2 Q3)。
停机 halt():
java
// BatchEventProcessor#halt
public void halt()
{
running.set(false);
sequenceBarrier.alert(); // 让正在 waitFor 的线程抛出 AlertException 退出
}
5.8 步骤八:Sequence 推进的三种写语义
把 Sequence 的三种写操作与它们的实际使用位置对照,是检验是否读懂源码的最好方式:
| 写操作 | 内存语义 | 屏障 | 使用位置 | 为什么用它 |
|---|---|---|---|---|
3.x :UNSAFE.putOrderedLong 4.0 :releaseFence + 普通写(set) |
lazySet / release | StoreStore | BatchEventProcessor 推进消费进度、SingleProducerSequencer.publish 推 cursor、MultiProducerSequencer.setAvailableBufferValue |
只要"后续的写不重排到它之前",不需要立即可见 → 最快 |
3.x :UNSAFE.putLongVolatile 4.0 :releaseFence + 写 + fullFence(setVolatile) |
volatile 写 | StoreLoad | SingleProducerSequencer.next 中发布 nextValue、Disruptor 构建期 |
需要让其它核心立刻看到,且后续还有加载 → 用全屏障 |
compareAndSet(4.0:VALUE_FIELD.compareAndSet) |
全屏障 | 全 | MultiProducerSequencer.next 抢 cursor、RingBuffer.addGatingSequences 更新 gating |
需要原子读改写 |
4.0 新增 setRelease / getAcquire |
release / acquire | 单向 | MultiProducerSequencer.availableBuffer 的写与读 |
成对使用即可保证数据可见,比 volatile 便宜 |
一句话概括 :Disruptor 的性能,一半来自"少用锁",另一半来自"精确控制内存屏障的强度"。 该 lazySet 的地方绝不用 volatile,该 volatile 的地方绝不省。这是"无锁编程"从能用走向高效的必经之路。
6. 关键类关系图
#mermaid-svg-GWCtxPaoIXac1bzM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-GWCtxPaoIXac1bzM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-GWCtxPaoIXac1bzM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-GWCtxPaoIXac1bzM .error-icon{fill:#552222;}#mermaid-svg-GWCtxPaoIXac1bzM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-GWCtxPaoIXac1bzM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-GWCtxPaoIXac1bzM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-GWCtxPaoIXac1bzM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-GWCtxPaoIXac1bzM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-GWCtxPaoIXac1bzM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-GWCtxPaoIXac1bzM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-GWCtxPaoIXac1bzM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-GWCtxPaoIXac1bzM .marker.cross{stroke:#333333;}#mermaid-svg-GWCtxPaoIXac1bzM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-GWCtxPaoIXac1bzM p{margin:0;}#mermaid-svg-GWCtxPaoIXac1bzM g.classGroup text{fill:#9370DB;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-GWCtxPaoIXac1bzM g.classGroup text .title{font-weight:bolder;}#mermaid-svg-GWCtxPaoIXac1bzM .cluster-label text{fill:#333;}#mermaid-svg-GWCtxPaoIXac1bzM .cluster-label span{color:#333;}#mermaid-svg-GWCtxPaoIXac1bzM .cluster-label span p{background-color:transparent;}#mermaid-svg-GWCtxPaoIXac1bzM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-GWCtxPaoIXac1bzM .cluster text{fill:#333;}#mermaid-svg-GWCtxPaoIXac1bzM .cluster span{color:#333;}#mermaid-svg-GWCtxPaoIXac1bzM .nodeLabel,#mermaid-svg-GWCtxPaoIXac1bzM .edgeLabel{color:#131300;}#mermaid-svg-GWCtxPaoIXac1bzM .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-GWCtxPaoIXac1bzM .label text{fill:#131300;}#mermaid-svg-GWCtxPaoIXac1bzM .labelBkg{background:#ECECFF;}#mermaid-svg-GWCtxPaoIXac1bzM .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-GWCtxPaoIXac1bzM .classTitle{font-weight:bolder;}#mermaid-svg-GWCtxPaoIXac1bzM .node rect,#mermaid-svg-GWCtxPaoIXac1bzM .node circle,#mermaid-svg-GWCtxPaoIXac1bzM .node ellipse,#mermaid-svg-GWCtxPaoIXac1bzM .node polygon,#mermaid-svg-GWCtxPaoIXac1bzM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-GWCtxPaoIXac1bzM .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-GWCtxPaoIXac1bzM g.clickable{cursor:pointer;}#mermaid-svg-GWCtxPaoIXac1bzM g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-GWCtxPaoIXac1bzM g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-GWCtxPaoIXac1bzM .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-GWCtxPaoIXac1bzM .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-GWCtxPaoIXac1bzM .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-GWCtxPaoIXac1bzM .dashed-line{stroke-dasharray:3;}#mermaid-svg-GWCtxPaoIXac1bzM .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-GWCtxPaoIXac1bzM #compositionStart,#mermaid-svg-GWCtxPaoIXac1bzM .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-GWCtxPaoIXac1bzM #compositionEnd,#mermaid-svg-GWCtxPaoIXac1bzM .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-GWCtxPaoIXac1bzM #dependencyStart,#mermaid-svg-GWCtxPaoIXac1bzM .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-GWCtxPaoIXac1bzM #dependencyStart,#mermaid-svg-GWCtxPaoIXac1bzM .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-GWCtxPaoIXac1bzM #extensionStart,#mermaid-svg-GWCtxPaoIXac1bzM .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-GWCtxPaoIXac1bzM #extensionEnd,#mermaid-svg-GWCtxPaoIXac1bzM .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-GWCtxPaoIXac1bzM #aggregationStart,#mermaid-svg-GWCtxPaoIXac1bzM .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-GWCtxPaoIXac1bzM #aggregationEnd,#mermaid-svg-GWCtxPaoIXac1bzM .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-GWCtxPaoIXac1bzM #lollipopStart,#mermaid-svg-GWCtxPaoIXac1bzM .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-GWCtxPaoIXac1bzM #lollipopEnd,#mermaid-svg-GWCtxPaoIXac1bzM .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-GWCtxPaoIXac1bzM .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-GWCtxPaoIXac1bzM .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-GWCtxPaoIXac1bzM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-GWCtxPaoIXac1bzM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-GWCtxPaoIXac1bzM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 委托
cursor / gatingSequences
FixedSequenceGroup 聚合多个
newBarrier
每 Handler 一个
持有自己的进度
构造期创建事件
Disruptor
-RingBuffer<T> ringBuffer
-Executor executor
-ConsumerRepository consumerRepository
+handleEventsWith(EventHandler...) : EventHandlerGroup
+start() : RingBuffer<T>
+shutdown()
RingBuffer
-Object\[\] entries
-long indexMask
-Sequencer sequencer
+next() : long
+publish(long)
+get(long) : E
+publishEvent(EventTranslator, A)
+newPoller() : EventPoller
<<interface>>
Sequencer
+next(n) : long
+publish(long)
+newBarrier(Sequence...) : SequenceBarrier
+getMinimumSequence() : long
+getHighestPublishedSequence(long, long) : long
AbstractSequencer
#int bufferSize
#WaitStrategy waitStrategy
#Sequence cursor
#Sequence\[\] gatingSequences
SingleProducerSequencer
-long nextValue
-long cachedValue
+next(n) : long
MultiProducerSequencer
-int\[\] availableBuffer
-Sequence gatingSequenceCache
-int indexShift
+next(n) : long
+isAvailable(long) : boolean
Sequence
-volatile long value
+get() : long
+set(long)
+setVolatile(long)
+compareAndSet(long,long) : boolean
<<interface>>
SequenceBarrier
+waitFor(long) : long
+alert()
ProcessingSequenceBarrier
-Sequence dependentSequence
-WaitStrategy waitStrategy
FixedSequenceGroup
-Sequence\[\] sequences
+get() : long
<<interface>>
WaitStrategy
+waitFor(long, Sequence, Sequence, SequenceBarrier) : long
+signalAllWhenBlocking()
<<interface>>
EventProcessor
+run()
+halt()
+getSequence() : Sequence
BatchEventProcessor
-DataProvider<T> dataProvider
-EventHandler<T> eventHandler
-Sequence sequence
+run()
<<interface>>
EventHandler
+onEvent(T, long, boolean)
<<interface>>
EventFactory
+newInstance() : T
BusySpinWaitStrategy
YieldingWaitStrategy
SleepingWaitStrategy
BlockingWaitStrategy
时序图:一次完整的生产-消费
EventHandler BatchEventProcessor WaitStrategy MultiProducerSequencer RingBuffer 生产者线程 EventHandler BatchEventProcessor WaitStrategy MultiProducerSequencer RingBuffer 生产者线程 #mermaid-svg-4PWQS8WgTV5FaEEx{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4PWQS8WgTV5FaEEx .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4PWQS8WgTV5FaEEx .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4PWQS8WgTV5FaEEx .error-icon{fill:#552222;}#mermaid-svg-4PWQS8WgTV5FaEEx .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4PWQS8WgTV5FaEEx .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4PWQS8WgTV5FaEEx .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4PWQS8WgTV5FaEEx .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4PWQS8WgTV5FaEEx .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4PWQS8WgTV5FaEEx .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4PWQS8WgTV5FaEEx .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4PWQS8WgTV5FaEEx .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4PWQS8WgTV5FaEEx .marker.cross{stroke:#333333;}#mermaid-svg-4PWQS8WgTV5FaEEx svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4PWQS8WgTV5FaEEx p{margin:0;}#mermaid-svg-4PWQS8WgTV5FaEEx .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-4PWQS8WgTV5FaEEx text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-4PWQS8WgTV5FaEEx .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-4PWQS8WgTV5FaEEx .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-4PWQS8WgTV5FaEEx .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-4PWQS8WgTV5FaEEx .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-4PWQS8WgTV5FaEEx #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-4PWQS8WgTV5FaEEx .sequenceNumber{fill:white;}#mermaid-svg-4PWQS8WgTV5FaEEx #sequencenumber{fill:#333;}#mermaid-svg-4PWQS8WgTV5FaEEx #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-4PWQS8WgTV5FaEEx .messageText{fill:#333;stroke:none;}#mermaid-svg-4PWQS8WgTV5FaEEx .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-4PWQS8WgTV5FaEEx .labelText,#mermaid-svg-4PWQS8WgTV5FaEEx .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-4PWQS8WgTV5FaEEx .loopText,#mermaid-svg-4PWQS8WgTV5FaEEx .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-4PWQS8WgTV5FaEEx .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-4PWQS8WgTV5FaEEx .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-4PWQS8WgTV5FaEEx .noteText,#mermaid-svg-4PWQS8WgTV5FaEEx .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-4PWQS8WgTV5FaEEx .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-4PWQS8WgTV5FaEEx .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-4PWQS8WgTV5FaEEx .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-4PWQS8WgTV5FaEEx .actorPopupMenu{position:absolute;}#mermaid-svg-4PWQS8WgTV5FaEEx .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-4PWQS8WgTV5FaEEx .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-4PWQS8WgTV5FaEEx .actor-man circle,#mermaid-svg-4PWQS8WgTV5FaEEx line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-4PWQS8WgTV5FaEEx :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} dependentSequence.get() >= nextSequence ? loop 批量处理 publishEvent(translator, order) next() CAS(cursor: cur → cur+1) sequence = cur+1 translator.translateTo(entry, seq) 写入 entries publish(seq) availableBufferidx = seq >>> shift signalAllWhenBlocking() waitFor(nextSequence) availableSequence getHighestPublishedSequence(from, to) 连续可用上界 get(seq) event(复用对象) onEvent(event, seq, endOfBatch) sequence.set(availableSequence)
7. 等待策略全解与选型
7.1 决策树
#mermaid-svg-1aTl2GFP8YOWz8O3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1aTl2GFP8YOWz8O3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1aTl2GFP8YOWz8O3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1aTl2GFP8YOWz8O3 .error-icon{fill:#552222;}#mermaid-svg-1aTl2GFP8YOWz8O3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1aTl2GFP8YOWz8O3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1aTl2GFP8YOWz8O3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1aTl2GFP8YOWz8O3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1aTl2GFP8YOWz8O3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1aTl2GFP8YOWz8O3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1aTl2GFP8YOWz8O3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1aTl2GFP8YOWz8O3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1aTl2GFP8YOWz8O3 .marker.cross{stroke:#333333;}#mermaid-svg-1aTl2GFP8YOWz8O3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1aTl2GFP8YOWz8O3 p{margin:0;}#mermaid-svg-1aTl2GFP8YOWz8O3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1aTl2GFP8YOWz8O3 .cluster-label text{fill:#333;}#mermaid-svg-1aTl2GFP8YOWz8O3 .cluster-label span{color:#333;}#mermaid-svg-1aTl2GFP8YOWz8O3 .cluster-label span p{background-color:transparent;}#mermaid-svg-1aTl2GFP8YOWz8O3 .label text,#mermaid-svg-1aTl2GFP8YOWz8O3 span{fill:#333;color:#333;}#mermaid-svg-1aTl2GFP8YOWz8O3 .node rect,#mermaid-svg-1aTl2GFP8YOWz8O3 .node circle,#mermaid-svg-1aTl2GFP8YOWz8O3 .node ellipse,#mermaid-svg-1aTl2GFP8YOWz8O3 .node polygon,#mermaid-svg-1aTl2GFP8YOWz8O3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1aTl2GFP8YOWz8O3 .rough-node .label text,#mermaid-svg-1aTl2GFP8YOWz8O3 .node .label text,#mermaid-svg-1aTl2GFP8YOWz8O3 .image-shape .label,#mermaid-svg-1aTl2GFP8YOWz8O3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-1aTl2GFP8YOWz8O3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1aTl2GFP8YOWz8O3 .rough-node .label,#mermaid-svg-1aTl2GFP8YOWz8O3 .node .label,#mermaid-svg-1aTl2GFP8YOWz8O3 .image-shape .label,#mermaid-svg-1aTl2GFP8YOWz8O3 .icon-shape .label{text-align:center;}#mermaid-svg-1aTl2GFP8YOWz8O3 .node.clickable{cursor:pointer;}#mermaid-svg-1aTl2GFP8YOWz8O3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1aTl2GFP8YOWz8O3 .arrowheadPath{fill:#333333;}#mermaid-svg-1aTl2GFP8YOWz8O3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1aTl2GFP8YOWz8O3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1aTl2GFP8YOWz8O3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1aTl2GFP8YOWz8O3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1aTl2GFP8YOWz8O3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1aTl2GFP8YOWz8O3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1aTl2GFP8YOWz8O3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1aTl2GFP8YOWz8O3 .cluster text{fill:#333;}#mermaid-svg-1aTl2GFP8YOWz8O3 .cluster span{color:#333;}#mermaid-svg-1aTl2GFP8YOWz8O3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1aTl2GFP8YOWz8O3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1aTl2GFP8YOWz8O3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-1aTl2GFP8YOWz8O3 .icon-shape,#mermaid-svg-1aTl2GFP8YOWz8O3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1aTl2GFP8YOWz8O3 .icon-shape p,#mermaid-svg-1aTl2GFP8YOWz8O3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1aTl2GFP8YOWz8O3 .icon-shape .label rect,#mermaid-svg-1aTl2GFP8YOWz8O3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1aTl2GFP8YOWz8O3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1aTl2GFP8YOWz8O3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1aTl2GFP8YOWz8O3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是
否
能
不能
紧张
充裕
能
不能
如何选择 WaitStrategy?
对延迟敏感吗?
P99 要求 < 1ms ?
能保证消费者线程数
< CPU 物理核数吗?
CPU 资源紧张吗?
BusySpinWaitStrategy
极致低延迟
YieldingWaitStrategy
低延迟 + 让出 CPU
能接受 μs 级抖动吗?
SleepingWaitStrategy
默认推荐 ⭐
BlockingWaitStrategy
最省 CPU
LiteBlockingWaitStrategy
3.4+ 无锁阻塞
7.2 各策略源码要点
BlockingWaitStrategy :ReentrantLock + Condition,先看 cursor,不够再挂起。
java
@Override
public long waitFor(final long sequence, Sequence cursor, final Sequence dependentSequence,
final SequenceBarrier barrier) throws AlertException, InterruptedException
{
long availableSequence;
if ((availableSequence = cursor.get()) < sequence)
{
lock.lock();
try
{
while ((availableSequence = cursor.get()) < sequence)
{
barrier.checkAlert();
processorNotifyCondition.await(); // 挂起,等 signalAllWhenBlocking
}
}
finally
{
lock.unlock();
}
}
// 还要等前置消费者(依赖图场景)
while ((availableSequence = dependentSequence.get()) < sequence)
{
barrier.checkAlert();
}
return availableSequence;
}
@Override
public void signalAllWhenBlocking()
{
lock.lock();
try
{
processorNotifyCondition.signalAll();
}
finally
{
lock.unlock();
}
}
⚠️ 注意最后那个
while循环:它不加锁,纯自旋。这是为了处理"生产者已经发布了,但前置消费者还没追上来"的依赖场景。
YieldWaitStrategy 与 BusySpinWaitStrategy:区别只在中途是否让出 CPU。
java
// YieldingWaitStrategy
@Override
public long waitFor(...) throws AlertException, InterruptedException
{
long availableSequence;
int counter = spinTries; // 默认 100
while ((availableSequence = dependentSequence.get()) < sequence)
{
counter = applyWaitMethod(barrier, counter);
}
return availableSequence;
}
private int applyWaitMethod(final SequenceBarrier barrier, int counter) throws AlertException
{
barrier.checkAlert();
if (0 == counter)
{
Thread.yield(); // 让出 CPU 时间片
}
else
{
--counter;
Thread.onSpinWait();
}
return counter;
}
LiteBlockingWaitStrategy (3.4 新增):用 AtomicBoolean + LockSupport.park/unpark 替代重锁,标记为 "experimental" ,除非你能压测验证,否则不如直接用 BlockingWaitStrategy。
7.3 选型速查表
| 场景 | 推荐策略 | 理由 |
|---|---|---|
| 异步日志、业务异步解耦 | SleepingWaitStrategy |
CPU/延迟平衡最好,是大多数场景的默认选择 |
| 高频交易、撮合、行情分发 | BusySpinWaitStrategy |
延迟最低,需独占核 |
| 消费者数接近 CPU 核数 | YieldingWaitStrategy |
不独占核也能保持低延迟 |
| 资源受限、后台批处理 | BlockingWaitStrategy |
CPU 占用最低 |
| 上述都拿不准 | 先压测 | 唯一真理 |
关键认知 :没有任何一种策略在所有场景下都最优。 切换策略的成本是"改一行代码",收益可能是 3~5 倍。先跑基准测试,再定策略。
8. 实战代码 ★★★
8.1 依赖引入
xml
<!-- pom.xml -->
<properties>
<disruptor.version>4.0.0</disruptor.version>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependency>
<groupId>com.lmax</groupId>
<artifactId>disruptor</artifactId>
<version>${disruptor.version}</version>
</dependency>
<!-- 单元测试用(可选) -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<scope>test</scope>
</dependency>
groovy
// build.gradle
implementation 'com.lmax:disruptor:4.0.0'
⚠️ JDK 版本 :4.0.0 要求 Java 11+ 。若你的项目还停在 Java 8,只能用 3.4.4 (两者 API 在本文涉及的核心部分一致,只是 4.0 移除了
WorkerPool等)。注意com.lmax包名未变,v3 和 v4 不能同时存在于 classpath。💡 Log4j2 用户注意:Log4j2 官方因 Java 8 兼容性无法升级到 Disruptor 4.0,仍在使用 3.4.x。详见 \[前后端流式对话技术知识点]。
8.2 Hello World:最小可运行
java
package com.example.disruptor.demo;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.YieldingWaitStrategy;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import com.lmax.disruptor.util.DaemonThreadFactory;
/**
* 最小可运行示例:单生产者 → 单消费者
*/
public class HelloDisruptor
{
/** ① 事件:必须是可变 POJO,有无参构造,字段用普通 setter 原地写 */
public static class LongEvent
{
private long value;
public void set(long value)
{
this.value = value;
}
public long get()
{
return value;
}
}
public static void main(String[] args) throws InterruptedException
{
// ② 环形数组大小,必须是 2 的幂
int bufferSize = 1024;
// ③ 构造 Disruptor(4.0 起统一使用 ThreadFactory)
// 参数依次为:事件工厂 / 容量 / 线程工厂 / 生产者类型 / 等待策略
Disruptor<LongEvent> disruptor = new Disruptor<>(
LongEvent::new, // EventFactory:预创建事件对象
bufferSize,
DaemonThreadFactory.INSTANCE,
ProducerType.SINGLE, // 单线程发布 → 免 CAS 快路径
new YieldingWaitStrategy()); // 低延迟等待策略
// ④ 可选:异常处理
// ⚠️ 3.x 的 com.lmax.disruptor.dsl.LoggingExceptionHandler 在 4.0 已被删除,
// 4.0 内置只剩 FatalExceptionHandler(默认,抛异常杀死消费线程)
// 和 IgnoreExceptionHandler(静默忽略),生产环境请自定义(见 8.5)
disruptor.setDefaultExceptionHandler(new com.lmax.disruptor.ExceptionHandler<LongEvent>()
{
@Override
public void handleEventException(Throwable ex, long sequence, LongEvent event)
{
// 只记日志、不抛出,保证消费线程存活
ex.printStackTrace();
}
@Override
public void handleOnStartException(Throwable ex) { ex.printStackTrace(); }
@Override
public void handleOnShutdownException(Throwable ex) { ex.printStackTrace(); }
});
// ⑤ 注册消费者
disruptor.handleEventsWith((event, sequence, endOfBatch) ->
System.out.println("消费到 [" + event.get()
+ "] 序号=" + sequence + " 批次末尾=" + endOfBatch));
// ⑥ 启动消费线程
disruptor.start();
// ⑦ 生产者:从 RingBuffer 直接发布
RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer();
for (long i = 0; i < 10; i++)
{
// publishEvent 内部做了 next() → translateTo() → publish(),异常安全
ringBuffer.publishEvent((event, sequence) -> event.set(sequence));
}
// ⑧ 优雅停机:等待积压消费完再停(⚠️ 4.0 没有 shutdown(Duration) 重载)
disruptor.shutdown(5, java.util.concurrent.TimeUnit.SECONDS);
System.out.println("shutdown 完成");
}
}
⚠️ 4.0 变更 :com.lmax.disruptor.dsl.LoggingExceptionHandler 在 4.0 已被删除 ,内置实现只剩 FatalExceptionHandler(默认)和 IgnoreExceptionHandler,两者的日志输出已改为 JDK 9 的 System.Logger。生产环境请自行实现 ExceptionHandler(用 SLF4J 接你的日志系统),见 8.5。
8.3 订单异步处理:菱形依赖图
业务需求 :订单事件进来后 → ① 风控校验与 ② 审计留痕并行 → 两者都完成后 → ③ 落库 + ④ 推送通知。
#mermaid-svg-cz1PZUW1l4tF8PKd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cz1PZUW1l4tF8PKd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cz1PZUW1l4tF8PKd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cz1PZUW1l4tF8PKd .error-icon{fill:#552222;}#mermaid-svg-cz1PZUW1l4tF8PKd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cz1PZUW1l4tF8PKd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cz1PZUW1l4tF8PKd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cz1PZUW1l4tF8PKd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cz1PZUW1l4tF8PKd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cz1PZUW1l4tF8PKd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cz1PZUW1l4tF8PKd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cz1PZUW1l4tF8PKd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cz1PZUW1l4tF8PKd .marker.cross{stroke:#333333;}#mermaid-svg-cz1PZUW1l4tF8PKd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cz1PZUW1l4tF8PKd p{margin:0;}#mermaid-svg-cz1PZUW1l4tF8PKd .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cz1PZUW1l4tF8PKd .cluster-label text{fill:#333;}#mermaid-svg-cz1PZUW1l4tF8PKd .cluster-label span{color:#333;}#mermaid-svg-cz1PZUW1l4tF8PKd .cluster-label span p{background-color:transparent;}#mermaid-svg-cz1PZUW1l4tF8PKd .label text,#mermaid-svg-cz1PZUW1l4tF8PKd span{fill:#333;color:#333;}#mermaid-svg-cz1PZUW1l4tF8PKd .node rect,#mermaid-svg-cz1PZUW1l4tF8PKd .node circle,#mermaid-svg-cz1PZUW1l4tF8PKd .node ellipse,#mermaid-svg-cz1PZUW1l4tF8PKd .node polygon,#mermaid-svg-cz1PZUW1l4tF8PKd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cz1PZUW1l4tF8PKd .rough-node .label text,#mermaid-svg-cz1PZUW1l4tF8PKd .node .label text,#mermaid-svg-cz1PZUW1l4tF8PKd .image-shape .label,#mermaid-svg-cz1PZUW1l4tF8PKd .icon-shape .label{text-anchor:middle;}#mermaid-svg-cz1PZUW1l4tF8PKd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cz1PZUW1l4tF8PKd .rough-node .label,#mermaid-svg-cz1PZUW1l4tF8PKd .node .label,#mermaid-svg-cz1PZUW1l4tF8PKd .image-shape .label,#mermaid-svg-cz1PZUW1l4tF8PKd .icon-shape .label{text-align:center;}#mermaid-svg-cz1PZUW1l4tF8PKd .node.clickable{cursor:pointer;}#mermaid-svg-cz1PZUW1l4tF8PKd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cz1PZUW1l4tF8PKd .arrowheadPath{fill:#333333;}#mermaid-svg-cz1PZUW1l4tF8PKd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cz1PZUW1l4tF8PKd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cz1PZUW1l4tF8PKd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cz1PZUW1l4tF8PKd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cz1PZUW1l4tF8PKd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cz1PZUW1l4tF8PKd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cz1PZUW1l4tF8PKd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cz1PZUW1l4tF8PKd .cluster text{fill:#333;}#mermaid-svg-cz1PZUW1l4tF8PKd .cluster span{color:#333;}#mermaid-svg-cz1PZUW1l4tF8PKd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cz1PZUW1l4tF8PKd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cz1PZUW1l4tF8PKd rect.text{fill:none;stroke-width:0;}#mermaid-svg-cz1PZUW1l4tF8PKd .icon-shape,#mermaid-svg-cz1PZUW1l4tF8PKd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cz1PZUW1l4tF8PKd .icon-shape p,#mermaid-svg-cz1PZUW1l4tF8PKd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cz1PZUW1l4tF8PKd .icon-shape .label rect,#mermaid-svg-cz1PZUW1l4tF8PKd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cz1PZUW1l4tF8PKd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cz1PZUW1l4tF8PKd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cz1PZUW1l4tF8PKd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 生产者
OrderEventPublisher
RingBuffer
16384
风控 Handler
RiskEventHandler
审计 Handler
AuditEventHandler
落库 Handler
StoreEventHandler
通知 Handler
NotifyEventHandler
① 事件定义
java
package com.example.disruptor.order;
/**
* 订单事件:必须可复用 ------ 所有字段都是基本类型/String 引用,不做任何"只读快照"语义
*/
public class OrderEvent
{
private long orderId;
private long userId;
private long amountCent; // 金额用"分"存,避免浮点
private String productCode;
private int status; // 0=新建 1=风控通过 2=风控拒绝 3=已落库 4=已通知
public void reset()
{
this.orderId = 0L;
this.userId = 0L;
this.amountCent = 0L;
this.productCode = null;
this.status = 0;
}
// getters / setters 省略,实际项目请补全
public long getOrderId() { return orderId; }
public void setOrderId(long orderId) { this.orderId = orderId; }
public long getUserId() { return userId; }
public void setUserId(long userId) { this.userId = userId; }
public long getAmountCent() { return amountCent; }
public void setAmountCent(long amountCent) { this.amountCent = amountCent; }
public String getProductCode() { return productCode; }
public void setProductCode(String productCode) { this.productCode = productCode; }
public int getStatus() { return status; }
public void setStatus(int status) { this.status = status; }
@Override
public String toString()
{
return "OrderEvent{orderId=" + orderId + ", userId=" + userId
+ ", amountCent=" + amountCent + ", productCode='" + productCode
+ "', status=" + status + '}';
}
}
② 四个 Handler
java
package com.example.disruptor.order;
import com.lmax.disruptor.EventHandler;
import java.util.concurrent.atomic.AtomicLong;
/** 风控:只读金额/用户,决定是否拒绝 */
public class RiskEventHandler implements EventHandler<OrderEvent>
{
private final AtomicLong passed = new AtomicLong();
private final AtomicLong rejected = new AtomicLong();
@Override
public void onEvent(OrderEvent event, long sequence, boolean endOfBatch)
{
if (event.getAmountCent() > 5_000_000L) // 超过 5 万,拒绝
{
event.setStatus(2);
rejected.incrementAndGet();
}
else
{
event.setStatus(1);
passed.incrementAndGet();
}
// endOfBatch=true 时可以做一次批量埋点上报
if (endOfBatch)
{
System.out.printf("[风控] 批次结束,累计通过=%d 拒绝=%d%n",
passed.get(), rejected.get());
}
}
}
/** 审计:只读,写审计日志;不修改事件状态(并行分支中要避免写同一字段) */
public class AuditEventHandler implements EventHandler<OrderEvent>
{
@Override
public void onEvent(OrderEvent event, long sequence, boolean endOfBatch)
{
// 真实场景这里落审计表 / 发 Kafka
if (endOfBatch)
{
System.out.printf("[审计] 批次处理至序号 %d%n", sequence);
}
}
}
/** 落库:等风控 + 审计都完成后才执行 */
public class StoreEventHandler implements EventHandler<OrderEvent>
{
@Override
public void onEvent(OrderEvent event, long sequence, boolean endOfBatch)
{
if (event.getStatus() == 2)
{
// 风控拒绝的不落库
return;
}
// 真实场景:jdbcTemplate.update(...) / MyBatis 批量插入
event.setStatus(3);
if (endOfBatch)
{
System.out.printf("[落库] 批次落库至序号 %d%n", sequence);
}
}
}
/** 通知:落库后推送 */
public class NotifyEventHandler implements EventHandler<OrderEvent>
{
@Override
public void onEvent(OrderEvent event, long sequence, boolean endOfBatch)
{
if (event.getStatus() == 2)
{
return;
}
event.setStatus(4);
// 真实场景:WebSocket / 站内信 / 短信
if (endOfBatch)
{
System.out.printf("[通知] 批次推送到序号 %d%n", sequence);
}
}
}
③ 组装 + 运行
java
package com.example.disruptor.order;
import com.lmax.disruptor.BlockingWaitStrategy;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import com.lmax.disruptor.util.DaemonThreadFactory;
import java.util.concurrent.TimeUnit;
public class OrderDisruptorBootstrap
{
public static void main(String[] args) throws Exception
{
int bufferSize = 16_384; // 2 的 14 次方
Disruptor<OrderEvent> disruptor = new Disruptor<>(
OrderEvent::new, // 构造期会 new 出 16384 个 OrderEvent
bufferSize,
DaemonThreadFactory.INSTANCE,
ProducerType.MULTI, // 可能有多个线程发布(HTTP 线程池)
new BlockingWaitStrategy()); // 业务场景不需要极致延迟,省 CPU
// ===== 依赖编排:这是 Disruptor 最强大的地方 =====
// handleEventsWith(A, B) → A、B 并行(各自拿到同一份事件)
// .then(C) → 等 A、B 都处理完再执行 C
// .then(D) → C 完成后再执行 D
disruptor
.handleEventsWith(new RiskEventHandler(), new AuditEventHandler())
.then(new StoreEventHandler())
.then(new NotifyEventHandler());
disruptor.start();
RingBuffer<OrderEvent> ringBuffer = disruptor.getRingBuffer();
// 模拟 10 个线程并发发布订单,共 10 万条
Thread[] producers = new Thread[10];
for (int t = 0; t < producers.length; t++)
{
final int tid = t;
producers[t] = new Thread(() ->
{
for (int i = 0; i < 10_000; i++)
{
long orderId = tid * 10_000L + i;
// EventTranslatorOneArg:把 orderId 传进去,避免 lambda 捕获额外对象
ringBuffer.publishEvent((event, sequence, id) ->
{
event.reset(); // ⚠️ 必须重置,因为对象是复用的
event.setOrderId(id);
event.setUserId(id % 1000);
event.setAmountCent(1000L + (id % 100_000));
event.setProductCode("SKU-" + (id % 50));
}, orderId);
}
}, "producer-" + tid);
producers[t].start();
}
for (Thread p : producers)
{
p.join();
}
// 优雅停机:等待积压消费完(最多 10 秒)
disruptor.shutdown(10, TimeUnit.SECONDS);
System.out.println("全部处理完成");
}
}
⚠️ 两个必须注意的点:
event.reset()必须调用。事件对象是复用的,上一个事件残留的字段会污染当前事件。这是最容易出的生产事故。- 并行分支中的 Handler 不要写同一个字段 。
RiskEventHandler写status,AuditEventHandler就不能再写status------它们是并发的,没有顺序保证。
8.4 EventTranslator 三种写法
publishEvent 有多种重载,按参数个数的不同避免了 lambda 捕获时的装箱开销:
java
RingBuffer<OrderEvent> rb = disruptor.getRingBuffer();
// ① 无参:只用序号
rb.publishEvent((event, sequence) -> event.setOrderId(sequence));
// ② 单参:最常用,避免为传参多创建一个"捕获对象"
rb.publishEvent((event, sequence, orderId) -> {
event.setOrderId(orderId);
}, 1001L);
// ③ 双参
rb.publishEvent((event, sequence, userId, amount) -> {
event.setUserId(userId);
event.setAmountCent(amount);
}, 42L, 9999L);
// ④ 三参 / 任意参:用 EventTranslatorVararg
rb.publishEvent((event, sequence, args) -> {
event.setOrderId((Long) args[0]);
event.setUserId((Long) args[1]);
}, 1001L, 42L);
// ⑤ 非阻塞发布:缓冲区满时返回 false,由调用方决定是丢弃还是降级
boolean ok = rb.tryPublishEvent((event, sequence, id) -> event.setOrderId(id), 1001L);
if (!ok)
{
// 降级:落 DB / 落本地文件 / 直接丢弃
}
| 方法 | 满时行为 | 适用 |
|---|---|---|
publishEvent(...) |
自旋等待直到有空位 | 不能丢数据的场景 |
tryPublishEvent(...) |
立即返回 false |
可降级、可丢弃的场景(如埋点、行情快照) |
💡 更好的写法:把
EventTranslatorOneArg提取为静态常量或枚举单例,避免每次调用都创建一个 lambda 实例(虽然现代 JVM 会缓存无捕获 lambda,但捕获型 lambda 每次都是新对象):
javaprivate static final EventTranslatorOneArg<OrderEvent, Long> ORDER_TRANSLATOR = (event, sequence, orderId) -> { event.setOrderId(orderId); }; // 调用 ringBuffer.publishEvent(ORDER_TRANSLATOR, 1001L);
8.5 Spring Boot 集成
① 线程工厂(必加,否则线上排查时线程名全是 pool-N-thread-M)
java
package com.example.disruptor.config;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
public class NamedThreadFactory implements ThreadFactory
{
private final String prefix;
private final AtomicInteger counter = new AtomicInteger(1);
private final boolean daemon;
public NamedThreadFactory(String prefix)
{
this(prefix, true);
}
public NamedThreadFactory(String prefix, boolean daemon)
{
this.prefix = prefix;
this.daemon = daemon;
}
@Override
public Thread newThread(Runnable r)
{
Thread t = new Thread(r, prefix + "-" + counter.getAndIncrement());
t.setDaemon(daemon); // 守护线程:不阻塞 JVM 退出
t.setUncaughtExceptionHandler((thread, ex) ->
System.err.println("[" + thread.getName() + "] 未捕获异常: " + ex));
return t;
}
}
② 异常处理器(生产环境必备,替代默认的 FatalExceptionHandler)
java
package com.example.disruptor.config;
import com.lmax.disruptor.ExceptionHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 记录日志但不中断消费线程 ------ 默认的 FatalExceptionHandler 会让线程直接死掉
*/
public class LogAndContinueExceptionHandler<T> implements ExceptionHandler<T>
{
private static final Logger log = LoggerFactory.getLogger(LogAndContinueExceptionHandler.class);
@Override
public void handleEventException(Throwable ex, long sequence, T event)
{
// 注意:这里要快速返回,不要在消费线程里做重活(写 DB / 发 MQ)
log.error("处理事件异常 seq={} event={}", sequence, event, ex);
}
@Override
public void handleOnStartException(Throwable ex)
{
log.error("EventHandler onStart 异常", ex);
}
@Override
public void handleOnShutdownException(Throwable ex)
{
log.error("EventHandler onShutdown 异常", ex);
}
}
③ 配置类
java
package com.example.disruptor.config;
import com.example.disruptor.order.AuditEventHandler;
import com.example.disruptor.order.NotifyEventHandler;
import com.example.disruptor.order.OrderEvent;
import com.example.disruptor.order.RiskEventHandler;
import com.example.disruptor.order.StoreEventHandler;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.SleepingWaitStrategy;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DisruptorConfig
{
private static final int BUFFER_SIZE = 1 << 14; // 16384
/**
* destroyMethod = "shutdown" 会在容器关闭时调用 Disruptor#shutdown()
* ⚠️ 这只是"立即停机",不会等待积压消费完;
* 要优雅停机请用 @PreDestroy 手动调用带超时的 shutdown(见 OrderEventPublisher)
*/
@Bean(destroyMethod = "")
public Disruptor<OrderEvent> orderDisruptor()
{
Disruptor<OrderEvent> disruptor = new Disruptor<>(
OrderEvent::new,
BUFFER_SIZE,
new NamedThreadFactory("order-disruptor"),
ProducerType.MULTI,
new SleepingWaitStrategy());
disruptor.setDefaultExceptionHandler(new LogAndContinueExceptionHandler<>());
disruptor.handleEventsWith(new RiskEventHandler(), new AuditEventHandler())
.then(new StoreEventHandler())
.then(new NotifyEventHandler());
disruptor.start();
return disruptor;
}
@Bean
public RingBuffer<OrderEvent> orderRingBuffer(Disruptor<OrderEvent> orderDisruptor)
{
return orderDisruptor.getRingBuffer();
}
}
④ 发布服务(含优雅停机)
java
package com.example.disruptor.order;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.dsl.Disruptor;
import jakarta.annotation.PreDestroy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class OrderEventPublisher
{
private static final Logger log = LoggerFactory.getLogger(OrderEventPublisher.class);
private final RingBuffer<OrderEvent> ringBuffer;
private final Disruptor<OrderEvent> disruptor;
/** 复用同一个 Translator 实例,避免重复创建 */
private static final com.lmax.disruptor.EventTranslatorOneArg<OrderEvent, OrderRequest>
REQUEST_TRANSLATOR = (event, sequence, req) ->
{
event.reset();
event.setOrderId(req.orderId());
event.setUserId(req.userId());
event.setAmountCent(req.amountCent());
event.setProductCode(req.productCode());
};
public OrderEventPublisher(RingBuffer<OrderEvent> ringBuffer, Disruptor<OrderEvent> disruptor)
{
this.ringBuffer = ringBuffer;
this.disruptor = disruptor;
}
/** 发布订单事件(阻塞式,缓冲区满会自旋等待) */
public void publish(OrderRequest request)
{
ringBuffer.publishEvent(REQUEST_TRANSLATOR, request);
}
/** 尝试发布(非阻塞),适合可降级场景 */
public boolean tryPublish(OrderRequest request)
{
boolean ok = ringBuffer.tryPublishEvent(REQUEST_TRANSLATOR, request);
if (!ok)
{
log.warn("RingBuffer 已满,订单 {} 发布失败", request.orderId());
}
return ok;
}
/**
* 优雅停机:先停止接收新请求(由 Web 容器负责),
* 再等待 Disruptor 把积压消费完,最后才关闭
*/
@PreDestroy
public void shutdown()
{
log.info("开始关闭 Disruptor,剩余待消费事件数 ≈ {}",
ringBuffer.getCursor() - ringBuffer.getMinimumGatingSequence());
disruptor.shutdown(30, TimeUnit.SECONDS); // ⚠️ 4.0 无 shutdown(Duration) 重载
log.info("Disruptor 已关闭");
}
/** 入参 DTO(record 便于写示例,实际项目可换成普通类) */
public record OrderRequest(long orderId, long userId, long amountCent, String productCode) { }
}
⑤ 使用
java
@RestController
@RequestMapping("/api/order")
public class OrderController
{
private final OrderEventPublisher publisher;
public OrderController(OrderEventPublisher publisher)
{
this.publisher = publisher;
}
@PostMapping
public ResponseEntity<String> create(@RequestBody OrderEventPublisher.OrderRequest req)
{
publisher.publish(req);
return ResponseEntity.ok("accepted");
}
}
application.yml(配套参数)
yaml
disruptor:
order:
buffer-size: 16384 # 必须是 2 的幂
wait-strategy: sleeping # busy_spin / yielding / sleeping / blocking
shutdown-timeout-seconds: 30
producer-type: multi # single / multi
logging:
level:
com.example.disruptor: INFO
容量怎么定? 经验公式:
bufferSize ≥ 峰值 QPS × 消费者单条耗时(秒) × 安全系数(3~5)例:峰值 5000 QPS,消费耗时 2ms(含 DB 写入),则
5000 × 0.002 × 5 = 50→ 取 64 或 128 即可。但当容量较小时,生产者的 CAS 自旋和消费者的
waitFor都会更频繁地"绕圈",性能反而下降------不是越小越好,通常宁大勿小 (内存代价只是bufferSize × 对象大小)。
8.6 EventPoller:非阻塞拉取(Netty/推送场景)
EventPoller 让消费者不占用一个线程去阻塞等待 ,而是由外部事件循环(如 Netty 的 EventLoop)驱动"有数据就处理"。
java
package com.example.disruptor.poll;
import com.example.disruptor.order.OrderEvent;
import com.lmax.disruptor.EventPoller;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import com.lmax.disruptor.util.DaemonThreadFactory;
import com.lmax.disruptor.BusySpinWaitStrategy;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 场景:把 Disruptor 里的行情/消息推送给长连接客户端。
* 用 EventPoller 把"消费"变成"轮询",可以挂在 Netty EventLoop 上,
* 不为消费者单独占用阻塞线程。
*/
public class EventPollerDemo
{
public static void main(String[] args) throws Exception
{
Disruptor<OrderEvent> disruptor = new Disruptor<>(
OrderEvent::new, 1024, DaemonThreadFactory.INSTANCE,
ProducerType.MULTI, new BusySpinWaitStrategy());
// 只启动 RingBuffer,不注册任何阻塞式消费者
RingBuffer<OrderEvent> ringBuffer = disruptor.start();
// 创建 Poller(会占用一个 gating sequence,生产者会尊重它的进度)
EventPoller<OrderEvent> poller = ringBuffer.newPoller();
// Handler 返回 true 表示"还要继续轮询",返回 false 表示"本轮到此为止"
EventPoller.Handler<OrderEvent> handler = (event, sequence, endOfBatch) ->
{
System.out.println("poll 到 [" + event + "] seq=" + sequence
+ " endOfBatch=" + endOfBatch);
return true; // 继续消费
};
// 模拟外部驱动:Netty 场景下应挂在 channelRead / EventLoop 的定时任务上
ScheduledExecutorService driver = Executors.newSingleThreadScheduledExecutor();
driver.scheduleWithFixedDelay(() ->
{
try
{
// ⚠️ poller.poll 不是线程安全的,必须在同一个线程调用
EventPoller.PollState state = poller.poll(handler);
// state 取值:PROCESSING_EVENTS(正在处理,说明还没消费完)
// IDLE(空闲,无数据)
}
catch (Exception e)
{
e.printStackTrace();
}
}, 0, 1, TimeUnit.MILLISECONDS);
// 生产数据
for (long i = 0; i < 20; i++)
{
ringBuffer.publishEvent((e, seq) -> { e.reset(); e.setOrderId(seq); });
}
Thread.sleep(500);
driver.shutdown();
disruptor.shutdown(3, TimeUnit.SECONDS);
}
}
| 对比项 | BatchEventProcessor |
EventPoller |
|---|---|---|
| 驱动方式 | 独立线程阻塞 waitFor |
外部线程主动 poll |
| 线程占用 | 每消费者 1 线程 | 复用调用方线程 |
| 适用 | 通用业务异步 | 与事件循环(Netty/Reactor)集成、需要背压控制 |
| 线程安全 | 各自独立线程,安全 | poll() 非线程安全,必须单线程调用 |
| 注意 | --- | 使用 Poller 后不要 再对同一 RingBuffer 调 handleEventsWith 的阻塞消费者(可以,但要理解两者是独立的 gating) |
⚠️
Disruptor#start()在没有注册任何 handler 时也能用------它只是把 RingBuffer 准备好。上面这个例子里handleEventsWith完全没调用,只用了 Poller。
8.7 压测代码
最简压测:不用 JMH,直接数吞吐
java
package com.example.disruptor.bench;
import com.lmax.disruptor.*;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import com.lmax.disruptor.util.DaemonThreadFactory;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
/**
* Disruptor 吞吐压测:对比不同 WaitStrategy / ProducerType 的表现
* 运行:java -cp ... BenchDisruptor 10000000 16384 busy_spin multi
*/
public class BenchDisruptor
{
static final class ValueEvent
{
long value;
public void set(long v) { this.value = v; }
}
public static void main(String[] args) throws Exception
{
long iterations = args.length > 0 ? Long.parseLong(args[0]) : 10_000_000L;
int bufferSize = args.length > 1 ? Integer.parseInt(args[1]) : 16384;
String wsName = args.length > 2 ? args[2] : "busy_spin";
String ptName = args.length > 3 ? args[3] : "multi";
WaitStrategy ws = switch (wsName)
{
case "busy_spin" -> new BusySpinWaitStrategy();
case "yielding" -> new YieldingWaitStrategy();
case "sleeping" -> new SleepingWaitStrategy();
case "blocking" -> new BlockingWaitStrategy();
default -> throw new IllegalArgumentException("unknown: " + wsName);
};
ProducerType pt = "single".equals(ptName) ? ProducerType.SINGLE : ProducerType.MULTI;
Disruptor<ValueEvent> disruptor = new Disruptor<>(
ValueEvent::new, bufferSize, DaemonThreadFactory.INSTANCE, pt, ws);
AtomicLong consumed = new AtomicLong();
CountDownLatch done = new CountDownLatch(1);
disruptor.handleEventsWith((event, sequence, endOfBatch) ->
{
if (consumed.incrementAndGet() == iterations)
{
done.countDown();
}
});
RingBuffer<ValueEvent> rb = disruptor.start();
// 预热
for (int i = 0; i < 100_000; i++)
{
rb.publishEvent((e, seq) -> e.set(seq));
}
while (consumed.get() < 100_000) { Thread.onSpinWait(); }
consumed.set(0);
// 正式压测
long start = System.nanoTime();
for (long i = 0; i < iterations; i++)
{
rb.publishEvent((e, seq) -> e.set(seq));
}
done.await();
long costNs = System.nanoTime() - start;
double opsPerSec = iterations * 1_000_000_000.0 / costNs;
System.out.printf("策略=%s 生产者=%s 容量=%d 条数=%d%n",
wsName, ptName, bufferSize, iterations);
System.out.printf("总耗时=%.3f s 吞吐=%.0f ops/s 单条平均=%.1f ns%n",
costNs / 1e9, opsPerSec, costNs / (double) iterations);
disruptor.shutdown(5, TimeUnit.SECONDS);
}
}
运行参数建议(贴近生产环境):
bash
java -server \
-XX:+UseParallelGC \
-Xms2g -Xmx2g \
-XX:-RestrictContended \
-cp target/classes:disruptor-4.0.0.jar \
com.example.disruptor.bench.BenchDisruptor 10000000 16384 busy_spin multi
输出示例(不同策略对比,同一台机器)
| WaitStrategy | ProducerType | 吞吐 (ops/s) | 单条平均 (ns) |
|---|---|---|---|
| blocking | multi | ~4,500,000 | ~220 |
| sleeping | multi | ~12,000,000 | ~83 |
| yielding | multi | ~22,000,000 | ~45 |
| busy_spin | multi | ~28,000,000 | ~36 |
| busy_spin | single | ~45,000,000 | ~22 |
⚠️ 上表是示意量级(MacBook M 系列 / 单消费者 / 无业务逻辑),你的真实数字取决于 CPU、JVM 参数、事件对象大小、业务耗时 。这里想说明的是相对关系:
blocking → busy_spin有 5~6 倍差距;multi → single有 约 1.5 倍差距(省掉了 CAS);- 业务逻辑(一次 DB 写入 ~1ms)一旦加进来,这些差异会被完全淹没------此时更该关注的是消费者并行度和批量化。
用 JMH 更严谨(避免 JIT 预热、死代码消除等干扰):
xml
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-core</artifactId>
<version>1.37</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
<artifactId>jmh-generator-annprocess</artifactId>
<version>1.37</version>
<scope>test</scope>
</dependency>
java
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Warmup(iterations = 3, time = 2)
@Measurement(iterations = 5, time = 3)
@Fork(1)
@State(Scope.Benchmark)
public class DisruptorJmhBench
{
private Disruptor<BenchDisruptor.ValueEvent> disruptor;
private RingBuffer<BenchDisruptor.ValueEvent> ringBuffer;
@Setup(Level.Trial)
public void setup()
{
disruptor = new Disruptor<>(BenchDisruptor.ValueEvent::new, 16384,
DaemonThreadFactory.INSTANCE, ProducerType.SINGLE, new BusySpinWaitStrategy());
disruptor.handleEventsWith((e, seq, endOfBatch) -> { /* 空实现,只测框架开销 */ });
ringBuffer = disruptor.start();
}
@TearDown(Level.Trial)
public void tearDown() throws Exception
{
disruptor.shutdown(5, TimeUnit.SECONDS);
}
@Benchmark
public void publish()
{
ringBuffer.publishEvent((e, seq) -> e.set(seq));
}
}
8.8 4.0 新特性:批次回退 Rewind
适用场景 :下游(如数据库)临时不可用,希望整个批次回退重试,而不是"跳过这一条继续往下"。
三个新增类型 (包名都是 com.lmax.disruptor,注意不在 dsl 包下):
java
// ① 回退动作:只有两个取值
public enum RewindAction
{
REWIND, // 回退到本批次起点,重来
THROW // 放弃回退,抛给 ExceptionHandler
}
// ② 回退策略:返回 REWIND 还是 THROW,由你决定
public interface BatchRewindStrategy
{
RewindAction handleRewindException(RewindableException ex, int attempt);
}
// ③ 可回退的 Handler:onEvent 多声明了一个 RewindableException
public interface RewindableEventHandler<T> extends EventHandlerBase<T>
{
void onEvent(T event, long sequence, boolean endOfBatch)
throws RewindableException, Exception;
}
// ④ 触发回退的异常(注意:它直接继承 Throwable,不是 Exception)
public class RewindableException extends Throwable
{
public RewindableException(Throwable cause) { ... }
}
两种用法
java
import com.lmax.disruptor.BatchRewindStrategy;
import com.lmax.disruptor.RewindAction;
import com.lmax.disruptor.RewindableEventHandler;
import com.lmax.disruptor.RewindableException;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.dsl.ProducerType;
import com.lmax.disruptor.BlockingWaitStrategy;
import com.lmax.disruptor.RingBuffer;
import java.util.concurrent.TimeUnit;
public class RewindDemo
{
public static void main(String[] args) throws Exception
{
Disruptor<OrderEvent> disruptor = new Disruptor<>(
OrderEvent::new, 8192, new NamedThreadFactory("order-rewind"),
ProducerType.MULTI, new BlockingWaitStrategy());
// 兜底异常处理:回退次数用尽(THROW)后由它接手
disruptor.setDefaultExceptionHandler(new LogAndContinueExceptionHandler<>());
// 自定义回退策略:指数退避,最多重来 3 次
BatchRewindStrategy retry3Times = (rewindableException, attempt) ->
{
if (attempt >= 3)
{
return RewindAction.THROW; // 放弃,交给 ExceptionHandler
}
try
{
Thread.sleep(50L * attempt); // 退避:50ms / 100ms
}
catch (InterruptedException ignored)
{
Thread.currentThread().interrupt();
return RewindAction.THROW;
}
return RewindAction.REWIND; // 整批回退重来
};
// ⭐ 用法一:通过 DSL 一次性声明"策略 + 可回退 Handler"
// 注意参数顺序:策略在前,Handler 在后
disruptor.handleEventsWith(retry3Times, new StoreEventHandler2());
disruptor.start();
RingBuffer<OrderEvent> rb = disruptor.getRingBuffer();
for (long i = 0; i < 100; i++)
{
rb.publishEvent((event, seq) -> { event.reset(); event.setOrderId(seq); });
}
disruptor.shutdown(10, TimeUnit.SECONDS);
}
}
/** 批次回退型 Handler:DB 不可用时整批重来 */
class StoreEventHandler2 implements RewindableEventHandler<OrderEvent>
{
@Override
public void onEvent(OrderEvent event, long sequence, boolean endOfBatch)
throws RewindableException
{
if (!databaseAvailable())
{
// 抛出后:本批已处理的事件会被"撤回",下次从批次起点重新处理
throw new RewindableException(new RuntimeException("DB 不可用"));
}
// 正常落库
}
private boolean databaseAvailable()
{
return true;
}
}
⚠️ 使用前必须理解的三条语义
| 要点 | 说明 |
|---|---|
| 副作用必须可重放 | 回退意味着 onEvent 会被重复调用 。如果 Handler 里有"扣款""发短信"这类不可重入的操作,回退会造成重复执行。只把它用在幂等操作上。 |
| 回退代价与批次大小成正比 | 批次越大,回退时白做的工作越多。用 BatchEventProcessor 的最大批次大小参数限制单批条数(4.0 已把它变成构造器参数,不再是 setMaxBatchSize() 方法)。 |
attempt 从 1 开始 |
别写成 attempt > 3 之外还想叠加计数器------attempt 由框架传入,是唯一的重试依据。 |
⚠️ 4.0 新增能力 ,3.4.4 完全没有。它的价值在于:把"事务边界"从单条事件提升到整批事件 ------搭配
endOfBatch使用,可以实现"批内全成功才提交"的效果。若你的场景是"逐条重试"而非"整批重试",用普通的ExceptionHandler+ 自己重投递更合适。
9. 常见坑与 FAQ
9.1 十个必踩的坑
| # | 坑 | 后果 | 正确做法 |
|---|---|---|---|
| 1 | 在消费者里保存 event 引用 |
之后读到的是被覆盖后的新值 | 立即把需要的数据拷贝到自己的对象里 |
| 2 | 容量写成非 2 的幂(如 1000) | 启动时抛 IllegalArgumentException |
用 Util.ceilingNextPowerOfTwo(1000) = 1024 |
| 3 | 声明 SINGLE 但多线程发布 |
静默数据错乱,极难排查 | 拿不准用 MULTI |
| 4 | 忘记 event.reset() |
上一事件的残留字段污染当前事件 | 每个事件定义 reset() 并在 translator 里首行调用 |
| 5 | 用默认的 FatalExceptionHandler |
一次异常就杀死消费线程,链路彻底 hang 住 | 自定义 ExceptionHandler 记日志并继续 |
| 6 | 不调 shutdown() |
非守护线程阻止 JVM 退出;数据丢失 | @PreDestroy 里调 shutdown(timeout, unit) |
| 7 | 在 Handler 里做耗时操作(HTTP 调用、大事务) | 生产者被"卡"------缓冲区很快填满并自旋 | 拆成多个 Handler 阶段,或交给下游线程池 |
| 8 | 并行分支的 Handler 写同一字段 | 数据竞争,结果不确定 | 每个分支只写自己负责的字段 |
| 9 | start() 后还想改依赖图 |
checkNotStarted() 抛异常 |
所有 handleEventsWith 必须在 start() 之前 |
| 10 | 一个 JVM 建几十个 Disruptor 实例 | 线程数爆掉(每 handler 一线程) | 复用实例,或合并到少数几个 RingBuffer |
9.2 FAQ
Q1:Disruptor 和 Disruptor 的中文名"干扰器"是一回事吗?它和 Kafka/RocketMQ 是什么关系?
不是一回事。Disruptor 是进程内(In-Process)框架,没有网络、没有持久化、没有分区概念;Kafka/RocketMQ 是分布式 MQ ,天然带网络和持久化。二者是互补的:很多 MQ 的客户端/服务端内部也用 Disruptor 做"网络线程 → 业务线程"的解耦(例如 Log4j2 用 Disruptor 做异步日志,是同一个思路)。
Q2:Disruptor 能替代线程池吗?
不能完全替代,但可以替代**"线程池 + 有界队列"**这一个组合。区别在于:
| 维度 | ThreadPoolExecutor + ArrayBlockingQueue |
Disruptor |
|---|---|---|
| 任务模型 | 每个任务是独立对象 | 事件对象复用 |
| 消费顺序 | 无序(多线程争抢) | 严格按序号,可并行但有序可溯 |
| 扇出 | 一条任务只被一个线程处理 | 一条事件可被 N 个消费者各处理一遍(广播) |
| 依赖编排 | 不支持 | 支持(.then()) |
| 拒绝策略 | 可自定义 | tryPublishEvent 返回 false |
| GC 压力 | 高 | 极低 |
Q3:消费者抛异常会怎样?怎么自定义?
默认 FatalExceptionHandler 的行为是:打日志 + 包装成 RuntimeException 抛出 → BatchEventProcessor.run() 的 finally 执行 notifyShutdown() 并 running.set(false) → 该消费线程彻底退出 。此后 RingBuffer 很快写满,生产者全部自旋,整个链路 hang 住。
正确做法(生产环境必做):
java
disruptor.setDefaultExceptionHandler(new ExceptionHandler<OrderEvent>()
{
@Override
public void handleEventException(Throwable ex, long sequence, OrderEvent event)
{
// ① 记日志(含序号,便于用序号回溯数据)
log.error("处理失败 seq={} event={}", sequence, event, ex);
// ② 决定是否落库/告警/进死信队列
deadLetterService.save(sequence, event, ex);
// ③ 快速返回,不要抛异常,不要做重活
}
@Override
public void handleOnStartException(Throwable ex)
{
log.error("onStart 失败", ex);
}
@Override
public void handleOnShutdownException(Throwable ex)
{
log.error("onShutdown 失败", ex);
}
});
注意 BatchEventProcessor 的异常分支会执行 sequence.set(nextSequence); nextSequence++;------跳过这条事件继续往下,所以自定义 Handler 不抛出,就能保证链路存活。
Q4:为什么我换了 WaitStrategy 性能没变化?
三个常见原因:
- 生产者成了瓶颈 (见 5.5 细节 B)------RingBuffer 满时走的是硬编码的
LockSupport.parkNanos(1),与 WaitStrategy 无关。给足容量、加快消费者才能解决。 - 业务耗时淹没了框架开销 ------一次 DB 写入 1ms,框架省下的 50ns 毫无意义。此时应该做的是加消费者并行度 或批量化 DB 写入。
- CPU 核数不够 ------
BusySpinWaitStrategy需要独占核;如果消费者数 ≥ 核数,自旋反而抢占业务线程,性能下降。
Q5:RingBuffer 的容量到底设多大?
三条约束:
- 必须是 2 的幂(否则启动抛异常);
- 不小于"最大允许积压量" :
峰值QPS × 消费者单条耗时 × 安全系数; - 别太小:容量过小会导致生产者频繁 "绕圈等待",性能陡降。
经验值:1024 ~ 65536 。内存代价 = bufferSize × 事件对象大小(16K × 200B ≈ 3MB,完全可以接受)。
Q6:如何监控 Disruptor 的运行状态?
四个关键指标:
java
RingBuffer<OrderEvent> rb = disruptor.getRingBuffer();
long cursor = rb.getCursor(); // 生产者已发布到哪
long minGating = rb.getMinimumGatingSequence(); // 最慢消费者追到哪
long backlog = cursor - minGating; // ⭐ 积压量
double usage = (double) backlog / rb.getBufferSize(); // ⭐ 使用率
// 各消费者的进度(需要自己持有 Sequence 引用)
// 注意:Sequence 是在 handleEventsWith 时创建的,请提前保存引用
推荐用 Micrometer 暴露:
java
@Component
public class DisruptorMetrics
{
private final RingBuffer<OrderEvent> ringBuffer;
private final MeterRegistry registry;
public DisruptorMetrics(RingBuffer<OrderEvent> ringBuffer, MeterRegistry registry)
{
this.ringBuffer = ringBuffer;
this.registry = registry;
// 背压水位:> 80% 就该告警
Gauge.builder("disruptor.order.backlog.ratio", ringBuffer, rb ->
{
long backlog = rb.getCursor() - rb.getMinimumGatingSequence();
return (double) backlog / rb.getBufferSize();
})
.description("Disruptor 缓冲区积压比例")
.register(registry);
}
}
告警阈值建议:积压率 > 0.8 持续 30 秒 → 说明消费能力不足,需要扩容或优化消费者。
Q7:Disruptor 支持"一条消息只被一个消费者处理"(竞争消费)吗?
4.0 起不支持 。3.x 有 WorkerPool / WorkProcessor 和 handleEventsWithWorkerPool,4.0 因从未在 LMAX 内部使用而被移除。若需要该语义,两种替代方案:
- 单消费者 + 内部分发 :一个
BatchEventProcessor消费,内部按 key hash 派发到多个线程池(注意会引入新的队列); - 自实现 WorkerPool :多个
BatchEventProcessor共享同一个Sequence,谁抢到谁处理(需要自己处理序号推进的原子性)。
Q8:shutdown() 和 halt() 有什么区别?
| 方法 | 行为 | 用在哪 |
|---|---|---|
disruptor.shutdown() |
先等积压消费完 (轮询 hasBacklog()),再逐个 halt() |
优雅停机 |
disruptor.shutdown(timeout, unit) |
同上,但等待上限 timeout | 推荐,避免卡死 |
disruptor.halt() |
立即停止所有消费者,未消费的事件直接丢弃 | 紧急停止、测试清理 |
batchEventProcessor.halt() |
立即停止单个消费者 | 精细控制 |
⚠️
shutdown()的"等待积压"依赖ringBuffer.getCursor()与所有消费者getSequence()的比较。如果你的 Handler 里有死循环,shutdown()会永远等下去------所以务必带上 timeout。
Q9:为什么消费者里拿到的 endOfBatch 总是不对/总是 true?
endOfBatch = (sequence == availableSequence)。在低负载 场景下,生产者发一条、消费者立刻处理一条,availableSequence == nextSequence 恒成立,所以每条都是 endOfBatch = true。这不是 bug。高负载时才会出现"一批多条"。
所以:不要把业务逻辑正确性依赖于 endOfBatch 的批次大小假设,它只应该用于"能批量就批量"的性能优化(如批量 flush)。
Q10:Disruptor 与 netty 的 MpscQueue 该如何选?
| 维度 | Disruptor | JCTools MpscArrayQueue |
|---|---|---|
| 消费模型 | 严格有序、支持依赖图、支持广播 | 无序、单消费者 |
| 批量 | 支持 | 支持(drain) |
| 内存占用 | 高(padding + availableBuffer) | 低 |
| 延迟 | 极低 | 低 |
| 适用 | 需要多消费者编排、广播 | 简单的"多生产者 → 单消费者"桥接 |
若你只是要把 Netty 的 IO 线程数据交给一个业务线程,MpscArrayQueue 更轻;若要做"风控 + 审计 + 落库"的多级编排,Disruptor 无可替代。参见 \[netty知识梳理-使用kimi3]。
10. 参考资料
官方资源
- LMAX Disruptor GitHub 仓库
- Disruptor 官方文档站
- Disruptor 4.0.0 Release Notes
- Disruptor Changelog(含 3.x 完整变更历史)
- Disruptor: High performance alternative to bounded queues(官方技术论文)
- LMAX Disruptor 4.0.0 发布说明(维护者博客)
版本与依赖
上手路径:
- 跑通 8.2 的 Hello World,把
WaitStrategy换成四种各跑一遍,观察 CPU 占用和吞吐差异;- 把项目里一个"线程池 + 有界队列"的异步链路改成 Disruptor(先只用一个消费者),加上自定义
ExceptionHandler和积压监控;- 用 8.3 的菱形依赖图改造多级处理链路,再按 8.7 做一轮真实业务耗时下的压测------你会发现在有 DB I/O 的场景,瓶颈从来不在 Disruptor 上;
- 读
MultiProducerSequencer和Sequence的完整源码(不到 400 行),亲手写一个简化版 RingBuffer,是理解无锁编程最有效的练习。一句话记住 Disruptor :它把"队列"从"需要加锁的数据结构"重新定义成了"一组单调递增的序号 + 一块预分配的内存"。 理解了这句话,就理解了它为什么快。