The Disruptor and Mechanical Sympathy: Ring Buffers Against Queues
Most Java developers reach for a queue whenever two threads need to talk. It feels natural, and it usually works well. However, at the extreme end of performance, that habit can quietly become the most expensive line in your system.
The LMAX team discovered this while building a financial exchange. As a result, they designed the Disruptor, a ring buffer that outpaces Java’s best bounded queue by a wide margin. In this article, we will look at why it is fast, what the published numbers really show, and when you should leave it alone.
What Mechanical Sympathy Really Means
Martin Thompson, one of the Disruptor’s authors, popularised the term on his Mechanical Sympathy blog. The idea comes from motor racing: a driver who understands the machine gets more out of it. In software, it means designing code around how CPUs, caches and memory actually behave.
The LMAX technical paper uses the phrase in exactly this way. Instead of tuning an existing queue, the authors asked what modern hardware likes. Then they built a data structure around the answer.
Why Ordinary Queues Struggle
Contention is far more expensive than it looks
To show the cost, the paper increments a 64-bit counter 500 million times on a 2.4 GHz Intel Westmere EP. The work never changes. Only the coordination does.
| Method | Time (ms) |
|---|---|
| Single thread | 300 |
| Single thread with volatile write | 4,700 |
| Single thread with CAS | 5,700 |
| Single thread with lock | 10,000 |
| Two threads with CAS | 30,000 |
| Two threads with lock | 224,000 |
Source: LMAX Disruptor technical paper, section 2.1 (2011 hardware).
Look at the last row. Adding a second thread that fights for the lock turns 300 ms of work into 224,000 ms. In short, contended writes are the costliest operation in concurrent code, and queues are full of them.
Queues live at the extremes
Producers and consumers rarely run at the same pace. Consequently, a queue sits close to full or close to empty most of the time. That means the head, tail and size variables are written constantly, and they often share the same cache line. On top of that, a linked-list queue allocates a node for every item, which keeps the garbage collector busy.
How the Disruptor Works
One pre-allocated ring
All entries are created at startup and then reused. As a result, the garbage collector treats them as immortal. Because the array is contiguous, the CPU can also prefetch it efficiently. Furthermore, the ring size is a power of two, so finding a slot needs a cheap bit mask instead of a costly remainder operation.
Sequences instead of locks
Each producer and consumer tracks its own sequence number. A producer claims the next slot, writes into it, and then publishes by advancing a cursor. Meanwhile, consumers wait for that cursor, and producers watch consumer positions so the ring never wraps. With a single producer, the paper states that no locks or CAS operations are needed, only memory barriers.
Batching smooths out bursts
If a consumer falls behind, it simply processes everything up to the cursor in one pass. Therefore, a burst from the producer does not create a growing backlog. According to the authors, latency stays almost flat until the memory subsystem saturates, whereas queues show the familiar “J curve”.
What the Numbers Say
Latency: nanoseconds against milliseconds
The paper measures a three-stage pipeline, sending an event every microsecond. The mean latency per hop was 52 ns for the Disruptor and 32,757 ns for ArrayBlockingQueue. The tail tells the same story, since the worst case drops from about 5.07 ms to 0.18 ms.

One detail deserves a careful look. The abstract claims a difference of three orders of magnitude, yet the table works out to roughly 630 times (32,757 divided by 52). That is closer to 2.8 orders. The gap is still enormous, but it is better to quote the exact figure.
Throughput: a modern re-run
The project documentation also includes a newer run on an AMD EPYC 9374F with OpenJDK 11. Here, ArrayBlockingQueue is compared with Disruptor 3 and Disruptor 4 across five topologies.

| Topology | ABQ | Disruptor 3 | Disruptor 4 | Speedup |
|---|---|---|---|---|
| Unicast 1P-1C | 20.9 | 134.6 | 160.4 | 7.7x |
| Pipeline 1P-3C | 5.2 | 76.1 | 101.3 | 19.4x |
| Sequencer 3P-1C | 18.8 | 16.0 | 29.7 | 1.6x |
| Multicast 1P-3C | 2.4 | 68.2 | 70.0 | 29.7x |
| Diamond 1P-3C | 3.4 | 61.2 | 63.1 | 18.4x |
Speedup is calculated from the paper’s raw figures (Disruptor 4 divided by ABQ).
The pattern is clear: the more stages and consumers, the bigger the win. However, the sequencer row is the honest outlier. With three producers, Disruptor 3 was actually slower than the queue (16.0 against 18.8 million), and Disruptor 4 only reached 1.6 times. Multiple producers must race to claim slots, so the advantage shrinks.
Read these results with care
These figures come from the project’s own documentation, so they are the authors’ numbers rather than an independent audit. The paper also says results vary between JVM runs. For that reason, treat them as a direction, not a promise, and measure your own workload.
When Should You Use It?
For most business applications, a plain queue or an executor is perfectly fine. A hand-off that costs tens of microseconds rarely matters next to a database call. The Disruptor pays off when that hand-off is the bottleneck. In addition, its wait strategies trade CPU for latency, so a spinning consumer is not free.
Checklist: is the Disruptor worth a try?
- Microsecond latency and low jitter genuinely matter.
- Profiling shows queue hand-offs, not I/O, as the cost.
- Your events fit pre-allocated, reusable objects.
- You have one producer, or can live with a smaller gain.
- You can spare CPU for spinning or yielding consumers.
What We Have Learned
Queues are slow at the extremes because they mix three jobs together: storage, producer coordination and consumer notification. The Disruptor separates them. It uses a pre-allocated ring, per-thread sequences and batching, so threads rarely fight over the same memory. The published data shows up to about 630 times lower mean latency and 8 to 30 times higher throughput in multi-stage pipelines. Even so, multiple producers narrow the gap, the benchmarks are the authors’ own, and the pattern costs CPU and design effort. In short, measure first, then adopt it only where a queue is truly the bottleneck.
Sources
- LMAX Disruptor technical paper (all benchmark data)

