Definition
- Ring buffer – fixed-size array (power of two), pre-allocated, slots reused in place; index via bitmask (
sequence & (size - 1)). - Sequences – monotonically increasing CAS counters; producers claim slots, consumers track progress.
- Sequence barriers – consumers ask "how far can I safely read?" without spinning blindly or locking.
- Single-writer principle – one thread owns each piece of data, eliminating write contention.
- Cache-line padding – sequences padded to prevent false sharing between unrelated counters.
- Advantage over BlockingQueue – no lock contention, no per-element allocation, no GC pauses.
Picture a circular conveyor belt at National Bank of India with a fixed number of trays. A trade order goes into the next empty tray (never creating a new tray). Processing stations downstream (risk, journaling, matching) follow the belt, each watching counters rather than fighting over a shared lock.
Understanding the Concepts
What is the ring buffer?
A fixed-size array whose length is a power of two. Slot access is a bitmask: sequence & (size - 1). Slots hold reusable event objects, so almost no garbage is created.
How do sequences coordinate without locks?
A producer claims the next sequence via CAS, writes the event in place, then publishes. Each consumer has its own sequence tracking how far it has processed.
What is a sequence barrier?
The barrier tracks dependent sequences. A consumer asks "how far can I safely read?" and the barrier returns the highest available sequence. Wait strategies trade CPU for latency: busy-spin for lowest latency, blocking for lower CPU.
What is the single-writer principle?
When only one thread writes to a given piece of data, write contention is zero. The Disruptor applies this by giving each participant its own sequence.
Why pad sequences to prevent false sharing?
Two unrelated counters on the same cache line invalidate each other across cores. Padding each sequence to a full cache line (64 bytes) prevents this.
How does it compare to BlockingQueue?
| Feature | BlockingQueue | Disruptor |
|---------|---------------|-----------|
| Coordination | Lock (put/take share one lock) | CAS on independent sequences |
| Allocation | Per-element (wrapper/node) | Pre-allocated, reused in place |
| GC pressure | High | Near zero |
| Cache behaviour | Lock state bounces between cores | Padded sequences, no false sharing |
| Throughput | Millions/sec with effort | 10M+ events/sec routinely |
How do you express consumer dependencies?
Dependencies form a graph. A matching consumer can wait behind a journaling consumer and a replication consumer, so the event is durably recorded before it is acted upon.
What are common interview follow-up questions?
- How does the Disruptor handle backpressure?
- What wait strategies are available and when do you use each?
- How does the Disruptor compare to Chronicle Queue?
What are common mistakes?
A common mistake is not understanding the concept fully before using it in production banking applications.
Wrong approach:
package com.bank.service;
public class TransferService {
public void transfer(String fromAccount, String toAccount, double amount) {
// WRONG: using double for money
double balance = getBalance(fromAccount);
balance = balance - amount;
}
private double getBalance(String id) { return 10000.0; }
}
Correct approach:
package com.bank.service;
import java.math.BigDecimal;
public class TransferService {
public void transfer(String fromAccount, String toAccount, BigDecimal amount) {
// CORRECT: using BigDecimal for money
BigDecimal balance = getBalance(fromAccount);
balance = balance.subtract(amount);
}
private BigDecimal getBalance(String id) { return new BigDecimal("10000.00"); }
}
Always use BigDecimal for monetary calculations in banking systems to avoid floating-point precision errors.
Visual Overview
Disruptor Ring Buffer:
┌───┬───┬───┬───┬───┬───┬───┬───┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ size = 8 (power of 2)
└───┴───┴───┴───┴───┴───┴───┴───┘
▲ ▲
│ Consumer seq = 2 │ Producer seq = 5
│ (processed up to 2) │ (published up to 5)
slot index = sequence & (size - 1)
sequence 5 → slot 5 & 7 = 5
Consumer Dependency Graph:
Producer → [Risk Check] → [Matching Engine]
→ [Journaling] ↗
→ [Replication]↗
Matching waits for journaling AND replication
Single Writer: each sequence owned by one thread
Padding: each sequence padded to 64-byte cache line
Key Takeaway
The LMAX Disruptor is a lock-free ring buffer coordinated by sequences and barriers under a single-writer rule. Pre-allocated, reused slots create near-zero garbage. CAS-based sequences eliminate lock contention. Cache-line padding prevents false sharing.
It beats BlockingQueue because there is no shared lock, no per-element allocation, and no GC pauses.
At National Bank of India our trading platform uses the Disruptor's ring buffer between feed ingestion, risk validation, journaling, and matching, achieving millions of events per second with microsecond latency.
Remember one sentence: the Disruptor is a lock-free, pre-allocated ring buffer coordinated by sequences and barriers under a single-writer rule, which is how it reaches millions of events per second without BlockingQueue overhead.
---