Backend Development 12 min read

Unlock Ultra‑Low Latency with Disruptor: Architecture, Features & Tuning Guide

This article introduces the high‑performance in‑memory queue Disruptor, covering its architecture—including ring buffer, sequence, sequencer, and wait strategies—its key features such as multicast events and lock‑free concurrency, and provides practical tuning advice and a complete Java example.

macrozheng
macrozheng
macrozheng
Unlock Ultra‑Low Latency with Disruptor: Architecture, Features & Tuning Guide

1 Introduction

Disruptor is a popular in‑memory message queue originating from LMAX's research on concurrency, performance and non‑blocking algorithms. Unlike distributed queues it runs entirely in memory.

Disruptor is not a distributed queue; its architecture differs significantly. Below is an LMAX case diagram using Disruptor.

We now introduce core concepts of Disruptor architecture.

1.1 Ring Buffer

Ring Buffer is considered the main design of Disruptor, but since version 3.0 it only stores and updates data; in advanced scenarios it can be replaced by the user.

1.2 Sequence

Disruptor uses Sequence to identify component positions. Each Consumer holds a Sequence similar to AtomicLong but without false sharing.

False sharing: CPU caches load data in cache lines. When multiple threads modify different variables within the same cache line, the line is invalidated and must be reloaded, causing performance loss in multithreaded environments.

1.3 Sequencer

Sequencer is the true core, with SingleProducerSequencer and MultiProducerSequencer implementations, providing all concurrency algorithms for fast, accurate data transfer.

1.4 Sequence Barrier

Sequencer creates a Sequence Barrier that references the Sequencer’s Sequence and the consumer’s Sequence, deciding whether events are ready for processing.

1.5 Wait Strategy

How consumers wait for events.

1.6 Event Processor

Responsible for looping over events, owns consumer Sequence, with BatchEventProcessor implementation offering efficient loops and callbacks.

1.7 Event Handler

Interface implemented by users to represent Disruptor consumers.

2 Disruptor Features

2.1 Multicast Events

Disruptor can broadcast each event to multiple consumers, unlike typical queues where an event is consumed by a single consumer.

In the LMAX example, JournalConsumer, ReplicationConsumer and ApplicationConsumer all receive every message.

2.2 Consumer Dependency Graph

To support concurrent processing, some consumers must be coordinated. The “gating” feature ensures that certain consumers finish before others start.

Producers add related consumers via RingBuffer.addGatingConsumers(). Dependencies are expressed through a SequenceBarrier that holds the Sequences of preceding consumers.

In the example, ApplicationConsumer’s SequenceBarrier references JournalConsumer and ReplicationConsumer.

Sequencer must ensure that downstream consumer Sequences never fall behind the Ring Buffer.

2.3 Memory Pre‑allocation

Disruptor aims for low latency by reducing or eliminating memory allocation, especially avoiding stop‑the‑world pauses in Java.

Users can pre‑allocate event memory via an EventFactory that populates every Ring Buffer slot. When publishing, the API allows reuse of these pre‑created objects.

2.4 Lock‑free Concurrency

Disruptor achieves low latency using lock‑free algorithms with memory barriers and CAS; the only lock used is in BlockingWaitStrategy.

3 Tuning Options

3.1 Single vs Multi Producer

<code>Disruptor&lt;LongEvent&gt; disruptor = new Disruptor(
    factory,
    bufferSize,
    DaemonThreadFactory.INSTANCE,
    ProducerType.SINGLE,
    new BlockingWaitStrategy()
);</code>

ProducerType.SINGLE creates a single‑producer Sequencer; MULTI creates a multi‑producer Sequencer. Following the single‑writer principle yields the best performance. Benchmark results on an i7 Sandy Bridge MacBook Air show higher throughput for single producer.

3.2 Wait Strategies

1. BlockingWaitStrategy – default, uses lock and Condition.

2. SleepingWaitStrategy – uses LockSupport.parkNanos(1), no Condition signaling, suitable for scenarios with low latency requirements such as asynchronous logging.

3. YieldingWaitStrategy – busy‑spin with Thread.yield(), good when the number of EventHandlers matches CPU cores and low latency is required.

4. BusySpinWaitStrategy – highest performance, consumes CPU fully, suitable only when EventHandler count matches CPU cores and hyper‑threading is disabled.

4 Official Example

Simple example where a producer sends a long value to a consumer.

Define the Event

<code>public class LongEvent {
    private long value;
    public void set(long value) { this.value = value; }
    @Override public String toString() { return "LongEvent{value=" + value + '}'; }
}</code>

Define the EventFactory

<code>public class LongEventFactory implements EventFactory<LongEvent> {
    @Override public LongEvent newInstance() { return new LongEvent(); }
}</code>

Create the EventHandler

<code>public class LongEventHandler implements EventHandler<LongEvent> {
    @Override public void onEvent(LongEvent event, long sequence, boolean endOfBatch) {
        System.out.println("Event: " + event);
    }
}</code>

Publish Events

<code>public class LongEventMain {
    public static void main(String[] args) throws Exception {
        int bufferSize = 1024;
        Disruptor<LongEvent> disruptor = new Disruptor<>(LongEvent::new, bufferSize, DaemonThreadFactory.INSTANCE);
        disruptor.handleEventsWith((event, sequence, endOfBatch) -> System.out.println("Event: " + event));
        disruptor.start();
        RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer();
        ByteBuffer bb = ByteBuffer.allocate(8);
        for (long l = 0; ; l++) {
            bb.putLong(0, l);
            ringBuffer.publishEvent((event, seq, buffer) -> event.set(buffer.getLong(0)), bb);
            Thread.sleep(1000);
        }
    }
}</code>

5 Summary

As a high‑performance in‑memory queue, Disruptor offers valuable design ideas such as memory pre‑allocation and lock‑free concurrency, and its usage is straightforward, making it highly recommended.

Performance TuningDisruptorlow latencyRing BufferJava ConcurrencyIn-Memory Queue
macrozheng
Written by

macrozheng

Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.

0 followers
Reader feedback

How this landed with the community

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