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.

Programmer1970
Programmer1970
Programmer1970
Why Disruptor’s Ring Buffer Beats BlockingQueue: Zero‑Copy, False‑Sharing Elimination, and Cache‑Line Padding

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=1

This 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 CAS

With 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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

zero-copyDisruptorFalse sharingHigh throughputJava concurrencyBlockingQueueCache line padding
Programmer1970
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.