Why Disruptor’s Ring Buffer Beats BlockingQueue: Zero‑Copy, False‑Sharing Elimination, and Cache‑Line Padding
The article dissects how Disruptor’s ring‑buffer architecture outperforms traditional BlockingQueue by eliminating lock contention, dynamic allocation, and cache‑line interference through pre‑allocated zero‑copy buffers, CAS‑based sequencing, cache‑line padding, batch processing, and tailored wait strategies, achieving millions of TPS.
Performance data
LMAX Exchange builds its trading system on Disruptor and reaches 6 million transactions per second (TPS), while a comparable implementation using a traditional BlockingQueue falls far short.
BlockingQueue performance ceiling
Both ArrayBlockingQueue and LinkedBlockingQueue rely on two fundamentals: locks and dynamic memory allocation.
// Simplified core logic of ArrayBlockingQueue
final ReentrantLock lock;
private final Object[] items; // dynamic resize uses Arrays.copyOf
public void put(E e) throws InterruptedException {
lock.lock(); // lock, thread blocks
try {
while (count == items.length) notFull.await();
items[putIndex] = e; // write
} finally {
lock.unlock();
}
}The three fatal issues are:
Lock contention forces thread‑to‑kernel context switches, wasting CPU cycles.
Array or linked‑list expansion creates many short‑lived objects, increasing GC pressure.
Data movement across scattered heap objects causes cache misses.
Disruptor first blade: Ring buffer + pre‑allocation for zero‑copy
Disruptor stores events in a fixed‑size Object[] called a RingBuffer. All event objects are created once at start‑up:
// RingBuffer pre‑allocates all events
Object[] entries = new Object[bufferSize]; // bufferSize must be a power of two
for (int i = 0; i < bufferSize; i++) {
entries[i] = eventFactory.newInstance(); // pre‑fill
}Because no new occurs during steady‑state processing, there is no GC pause. The long sequence identifier never overflows, and slot lookup uses a bitwise mask instead of a modulo operation:
int index = (int)(sequence & (bufferSize - 1)); // e.g., bufferSize=1024, sequence=1025 → index=1This eliminates object allocation, garbage collection, and expensive modulo arithmetic.
Disruptor second blade: Sequence + CAS eliminates locks
Instead of a lock, Disruptor uses a cursor (producer sequence) and a per‑consumer sequence. In a single‑producer scenario the next slot is obtained without CAS:
nextSequence = cursor + 1; // no lock, no CASWith multiple producers, they compete via CAS on the shared cursor:
long nextSequence;
do {
nextSequence = cursor.get();
} while (!cursor.compareAndSet(nextSequence, nextSequence + 1));Consumers coordinate through a SequenceBarrier that returns the minimum of the producer cursor and all dependent consumer sequences, all performed with atomic operations and memory barriers—no thread blocks or kernel transitions.
Disruptor third blade: Cache‑line padding eliminates false sharing
False sharing occurs when independent variables reside on the same 64‑byte cache line, causing each write to invalidate the other core’s cache. Disruptor pads the Sequence object so that its value occupies a dedicated cache line:
public class Sequence {
// core data (8 B)
private volatile long value;
// left padding: 7 longs = 56 B
private long p1, p2, p3, p4, p5, p6, p7;
// right padding: 7 longs = 56 B
private long p9, p10, p11, p12, p13, p14, p15;
}Although this uses more memory (≈120 B per sequence), each thread’s critical variable never shares a cache line, removing the cache‑line‑invalidations that cripple multi‑threaded throughput. Experiments show a non‑padded version can be only one‑third to one‑fifth as fast.
Batch processing: amortising fixed overhead
Disruptor’s EventHandler can process a batch of events, collapsing multiple method calls and memory barriers into a single operation. For ten events:
Individual processing: 10 method calls + 10 memory barriers.
Batch processing: 1 method call + 1 memory barrier.
This linearises throughput as batch size grows.
Wait strategies: latency vs CPU trade‑offs
BlockingWaitStrategy : lock + condition variable, highest latency, lowest CPU usage – suitable for asynchronous logging where latency is not critical.
SleepingWaitStrategy : spin + yield + parkNanos, medium latency, low CPU – general purpose.
YieldingWaitStrategy : 100 spins + yield, low latency, medium CPU – best when thread count < CPU cores.
BusySpinWaitStrategy : pure spin, lowest latency, extremely high CPU – only when a thread can be bound to a dedicated core.
For ultra‑low‑latency financial workloads, the yielding or busy‑spin strategies are typical, but busy‑spin should be used only when spare cores are available.
Multi‑consumer dependency graph
Disruptor can express complex consumer dependencies directly:
disruptor.handleEventsWith(c1, c2, c3).then(c4);In a real order‑processing scenario, risk check, inventory deduction, and logging can run in parallel (c1‑c3); the shipping notification (c4) fires only after all three complete. Implementing the same with a BlockingQueue would require additional coordination primitives such as CountDownLatch or CompletableFuture, adding complexity and overhead.
When to choose Disruptor vs. BlockingQueue
Prefer Disruptor when you need:
Prefer BlockingQueue when you have:
Disruptor’s steep learning curve—requiring power‑of‑two buffer sizes, predefined event types, and pre‑registered consumers—means frequent business‑logic changes can incur high refactoring cost.
Summary of key design points
Ring buffer + pre‑allocation → zero‑copy, no GC.
Sequence + CAS → lock‑free coordination in user space.
Cache‑line padding → eliminates false sharing.
Bit‑mask indexing → avoids modulo overhead.
Batch processing → amortises fixed costs, linear throughput growth.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer1970
Formerly called 'Code to 35'. Add our main WeChat ID to access a wealth of shared resources (algorithms, interview prep, tech stacks: Java, Python, Go, big data). We mainly share serious development techniques, focusing on output-driven input. Occasionally we post life snippets and gossip. Our aim is to attract precise traffic and test advertising opportunities.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
