Unlock Ultra‑Fast Java Concurrency with the Disruptor Framework

Disruptor is an open‑source, lock‑free Java framework that uses a ring‑buffer and sequencer to achieve ultra‑high‑throughput, handling millions of events per second, and is employed by projects like LMAX, Storm, Camel, and Log4j2, with detailed concepts, wait strategies, and usage examples.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Unlock Ultra‑Fast Java Concurrency with the Disruptor Framework

Disruptor is an open‑source framework originally proposed by LMAX to solve queue lock problems under high concurrency, capable of processing six million orders per second in a single thread.

Official site: http://lmax-exchange.github.io/disruptor/

Why Disruptor Was Created

Current Java built‑in queue thread‑safety mechanisms:

ArrayBlockingQueue – array‑based queue with locks.

LinkedBlockingQueue – linked‑list queue with locks.

ConcurrentLinkedQueue – linked‑list queue using CAS.

Because locking often severely degrades performance, a lock‑free solution like Disruptor was developed.

Core Concepts

RingBuffer – the underlying data structure, a circular array that serves as the exchange point between threads.

Sequencer – manages sequence numbers for producers and consumers, coordinating synchronization.

Sequence – a numeric marker used to track the position of events in the RingBuffer and consumer progress.

SequenceBarrier – coordinates producer cursor and consumer sequences to prevent overwriting unprocessed events.

EventProcessor – listens to RingBuffer events and hands them to the actual consumer implementation.

EventHandler – the business‑logic consumer interface implemented by users.

Producer – the interface for threads that publish events into the RingBuffer.

Wait Strategy – determines how a consumer waits for producers to publish events.

Wait Strategies

BlockingWaitStrategy

The default strategy uses locks and conditions; it has the lowest CPU usage but is the least efficient.

SleepingWaitStrategy

Similar performance to BlockingWaitStrategy but minimizes impact on producer threads by using LockSupport.parkNanos(1) for waiting.

YieldingWaitStrategy

Suitable for low‑latency systems; it spins and calls Thread.yield() to allow other threads to run, recommended when the number of consumer threads is less than CPU cores.

BusySpinWaitStrategy

Provides the best performance for ultra‑low‑latency scenarios, again best when consumer threads are fewer than CPU cores.

PhasedBackoffWaitStrategy

Combines spinning, yielding, and a custom back‑off; used when CPU resources are scarce and throughput/latency are not critical.

Usage Example

<dependency>
      <groupId>com.lmax</groupId>
      <artifactId>disruptor</artifactId>
      <version>3.3.4</version>
   </dependency>
// Define the event type exchanged via Disruptor.
public class LongEvent {
    private Long value;
    public Long getValue() { return value; }
    public void setValue(Long value) { this.value = value; }
}
public class LongEventFactory implements EventFactory<LongEvent> {
    public LongEvent newInstance() { return new LongEvent(); }
}
public class LongEventHandler implements EventHandler<LongEvent> {
    public void onEvent(LongEvent event, long sequence, boolean endOfBatch) throws Exception {
        System.out.println("Consumer:" + event.getValue());
    }
}
public class LongEventProducer {
    public final RingBuffer<LongEvent> ringBuffer;
    public LongEventProducer(RingBuffer<LongEvent> ringBuffer) { this.ringBuffer = ringBuffer; }
    public void onData(ByteBuffer byteBuffer) {
        long sequence = ringBuffer.next();
        Long data = null;
        try {
            LongEvent longEvent = ringBuffer.get(sequence);
            data = byteBuffer.getLong(0);
            longEvent.setValue(data);
            try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
        } finally {
            System.out.println("Producer ready to send data");
            ringBuffer.publish(sequence);
        }
    }
}
public class DisruptorMain {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        EventFactory<LongEvent> eventFactory = new LongEventFactory();
        int ringBufferSize = 1024 * 1024; // must be power of 2
        Disruptor<LongEvent> disruptor = new Disruptor<>(eventFactory, ringBufferSize, executor,
                ProducerType.SINGLE, new YieldingWaitStrategy());
        disruptor.handleEventsWith(new LongEventHandler());
        disruptor.start();
        RingBuffer<LongEvent> ringBuffer = disruptor.getRingBuffer();
        LongEventProducer producer = new LongEventProducer(ringBuffer);
        ByteBuffer byteBuffer = ByteBuffer.allocate(8);
        for (int i = 1; i <= 100; i++) {
            byteBuffer.putLong(0, i);
            producer.onData(byteBuffer);
        }
        disruptor.shutdown();
        executor.shutdown();
    }
}

Core Design Principles

Ring‑array structure: Uses an array instead of a linked list to avoid garbage collection and to be cache‑friendly.

CPU caches consist of cache lines (typically 64 bytes). A Java long is 8 bytes, so a cache line holds eight longs. When a long array element is loaded, the neighboring seven are also cached, enabling very fast traversal.

Element positioning: Array length is a power of two (2ⁿ); bitwise operations accelerate index calculation, and the index is a long, preventing overflow even at massive QPS.

Lock‑free design: Producers and consumers claim a slot in the array and write/read directly, with atomic CAS ensuring thread safety.

Data Structure

The framework uses a RingBuffer as the queue data structure, a customizable circular array.

In addition to the array, a sequence number points to the next available element for producers and consumers.

Sequence

Disruptor numbers events with an incrementing sequence; processing always follows increasing order.

What is the advantage of the array + sequence design?

Like a HashMap, knowing the index gives O(1) access. The index can be computed as index = sequence % table.length or via bitwise operations when the length is a power of two.

Write Data Flow

Request to write m elements.

If m slots are available, the maximum sequence number is returned, ensuring no overwrite of unread elements.

Producer writes the elements.

Use Cases

Tests show Disruptor’s latency and throughput far surpass ArrayBlockingQueue, making it a candidate when the latter becomes a performance bottleneck.

Typical scenario: a producer‑consumer pattern with one producer and multiple consumers that must process events in order.

Example: reading sequentially from MySQL binlog and writing to Elasticsearch; the binlog requires a single producer, while Elasticsearch demands ordered writes, which Disruptor can handle without locking.

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.

JavaconcurrencyDisruptorlock‑freeRingBufferWaitStrategy
Su San Talks Tech
Written by

Su San Talks Tech

Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.

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.