Disruptor: Simple Usage and Core Components of a High-Performance Lock-Free Queue in Java
This article introduces Java's Disruptor framework as a high-performance, lock-free queue, explains its core components such as RingBuffer, Sequencer, and WaitStrategy, provides sample code for producers and consumers, and discusses how it avoids lock contention and false sharing.
Java provides several queue implementations such as ArrayBlockingQueue (uses ReentrantLock ), LinkedBlockingQueue (uses ReentrantLock ), ConcurrentLinkedQueue (uses CAS), and others.
Because lock‑based queues have higher contention, the article introduces the Disruptor framework, an open‑source, lock‑free concurrency library developed by LMAX that can process millions of orders per second and is used internally by Log4j2 for asynchronous logging. Its limitation is that it is an in‑memory queue and cannot be used directly in distributed scenarios.
Simple usage example
Data transfer object:
@Data
public class EventData {
private Long value;
}Consumer implementation:
public class EventConsumer implements WorkHandler
{
/**
* Consumer callback
* @param eventData
* @throws Exception
*/
@Override
public void onEvent(EventData eventData) throws Exception {
Thread.sleep(5000);
System.out.println(Thread.currentThread() + ", eventData:" + eventData.getValue());
}
}Producer implementation:
public class EventProducer {
private final RingBuffer
ringBuffer;
public EventProducer(RingBuffer
ringBuffer) {
this.ringBuffer = ringBuffer;
}
public void sendData(Long v) {
long next = ringBuffer.next(); // CAS slot reservation
try {
EventData eventData = ringBuffer.get(next);
eventData.setValue(v);
} finally {
System.out.println("EventProducer send success, sequence:" + next);
ringBuffer.publish(next);
}
}
}Test class that wires everything together:
public class DisruptorTest {
public static void main(String[] args) {
int bufferSize = 8; // power of 2
Disruptor
disruptor = new Disruptor<>(
EventData::new,
bufferSize,
Executors.defaultThreadFactory(),
ProducerType.MULTI,
new BlockingWaitStrategy());
disruptor.handleEventsWithWorkerPool(
new EventConsumer(),
new EventConsumer(),
new EventConsumer(),
new EventConsumer());
disruptor.start();
RingBuffer
ringBuffer = disruptor.getRingBuffer();
EventProducer eventProducer = new EventProducer(ringBuffer);
long i = 0;
for (;;) {
i++;
eventProducer.sendData(i);
try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
}Core components of Disruptor
RingBuffer : a circular array that pre‑allocates entries to avoid object allocation during runtime; only updates existing entries.
Sequencer : the central coordinator that tracks the current write cursor, the minimum consumer sequence, and manages multi‑producer coordination. It provides SingleProducerSequencer and MultiProducerSequencer implementations.
Sequence : essentially an AtomicLong that records progress; padded to avoid false sharing.
WorkProcessor : runs a loop that obtains events from the RingBuffer and hands them to user‑defined WorkHandler implementations.
EventHandler : user‑implemented handler that contains business logic.
WaitStrategy : defines how consumers wait for new events. Available strategies include:
SleepingWaitStrategy : spin → yield → sleep
BlockingWaitStrategy : lock‑based waiting, suitable when CPU resources are scarce
YieldingWaitStrategy : spin → yield → spin
BusySpinWaitStrategy : pure spin, minimizes context switches
PhasedBackoffWaitStrategy : spin → yield → custom back‑off
How multi‑producer coordination works
Each producer obtains a range of slots by atomically incrementing the cursor (CAS). It checks the wrap‑point against the minimum consumer sequence to ensure it does not overrun unconsumed data. The availableBuffer int array marks which slots are ready for consumption; producers set the slot to 1 after publishing.
Consumers read the highest published sequence via Sequencer.getHighestPublishedSequence , which scans the availableBuffer until it finds an unavailable slot, thereby determining the safe upper bound for consumption.
False sharing problem and solution
False sharing occurs when multiple threads modify variables that reside on the same cache line, causing cache invalidation. Disruptor mitigates this by padding critical fields (e.g., Sequence ) with seven additional long values before and after the actual value, ensuring each frequently updated field occupies its own cache line. Java 8 also provides the @sun.misc.Contended annotation for the same purpose.
Recap
Cache‑line padding avoids frequent cache invalidation.
Lock‑free coordination via CAS (two‑phase commit).
Circular array (RingBuffer) eliminates garbage collection pressure.
Heavy use of bit‑wise operations for maximum throughput.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
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.