Backend Development 13 min read

Using LMAX Disruptor for High‑Performance In‑Memory Messaging in Java

This article introduces the LMAX Disruptor library, explains its core concepts such as Ring Buffer, Sequencer and Wait Strategy, and provides a step‑by‑step Java demo with full source code to build a fast, lock‑free in‑memory message queue.

Architect's Tech Stack
Architect's Tech Stack
Architect's Tech Stack
Using LMAX Disruptor for High‑Performance In‑Memory Messaging in Java

Background

In a recent project we needed a high‑throughput message queue and chose Disruptor instead of Kafka or RabbitMQ because of its speed and open‑source nature.

What is Disruptor?

Disruptor is a high‑performance queue developed by LMAX, a UK foreign‑exchange trading company, to solve latency problems of in‑memory queues. It can handle up to 6 million orders per second in a single‑threaded setup and has attracted industry attention since its 2010 QCon presentation.

It is an open‑source Java framework designed to achieve maximum throughput (TPS) and minimal latency for the producer‑consumer problem.

Functionally it implements a bounded queue, making it suitable for any producer‑consumer scenario.

LMAX uses Disruptor as a core component of its trading platform, achieving 6 million TPS; the same performance gains can be realized in many other applications.

Beyond being a framework, Disruptor represents a design philosophy for programs that involve concurrency, buffering, producer‑consumer models, or transaction processing.

GitHub: https://github.com/LMAX-Exchange/disruptor

Core Concepts of Disruptor

1. Ring Buffer – a circular buffer that stores events. Since version 3.0 its responsibility is limited to storing and updating events; users can provide custom implementations.

2. Sequence – a monotonically increasing number that identifies each event and tracks consumer progress, helping avoid false sharing between CPUs.

3. Sequencer – the heart of Disruptor; implementations include SingleProducerSequencer and MultiProducerSequencer to coordinate producers and consumers.

4. Sequence Barrier – links the main published sequence of the RingBuffer with dependent consumer sequences and determines whether a consumer can process more events.

5. Wait Strategy – defines how a consumer waits for the next event (e.g., blocking, sleeping, yielding), with different strategies offering varied performance characteristics.

6. Event – the data exchanged between producer and consumer; its type is defined by the user.

7. EventProcessor – holds a consumer’s sequence and runs the event‑handling loop.

8. EventHandler – user‑implemented interface that processes each event.

9. Producer – user code that publishes events to the Disruptor; Disruptor does not define a specific interface for it.

Demo – Step‑by‑Step Implementation

1. Add Maven dependency

<dependency>
    <groupId>com.lmax</groupId>
    <artifactId>disruptor</artifactId>
    <version>3.4.4</version>
</dependency>

2. Define the message model

/**
 * Message body
 */
@Data
public class MessageModel {
    private String message;
}

3. Create an EventFactory

public class HelloEventFactory implements EventFactory
{
    @Override
    public MessageModel newInstance() {
        return new MessageModel();
    }
}

4. Implement the consumer ( EventHandler )

@Slf4j
public class HelloEventHandler implements EventHandler
{
    @Override
    public void onEvent(MessageModel event, long sequence, boolean endOfBatch) {
        try {
            Thread.sleep(1000); // simulate async processing
            log.info("Consumer start processing");
            if (event != null) {
                log.info("Consumed message: {}", event);
            }
        } catch (Exception e) {
            log.info("Consumer failed");
        }
        log.info("Consumer end processing");
    }
}

5. Bean manager to expose the Spring ApplicationContext

@Component
public class BeanManager implements ApplicationContextAware {
    private static ApplicationContext applicationContext = null;
    @Override
    public void setApplicationContext(ApplicationContext ctx) throws BeansException {
        this.applicationContext = ctx;
    }
    public static ApplicationContext getApplicationContext() { return applicationContext; }
    public static Object getBean(String name) { return applicationContext.getBean(name); }
    public static
T getBean(Class
clazz) { return applicationContext.getBean(clazz); }
}

6. Configure the Disruptor and expose the RingBuffer

@Configuration
public class MQManager {
    @Bean("messageModel")
    public RingBuffer
messageModelRingBuffer() {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        HelloEventFactory factory = new HelloEventFactory();
        int bufferSize = 1024 * 256; // must be power of two
        Disruptor
disruptor = new Disruptor<>(factory, bufferSize, executor,
                ProducerType.SINGLE, new BlockingWaitStrategy());
        disruptor.handleEventsWith(new HelloEventHandler());
        disruptor.start();
        return disruptor.getRingBuffer();
    }
}

7. Service interface and implementation (producer)

public interface DisruptorMqService {
    void sayHelloMq(String message);
}

@Slf4j
@Component
@Service
public class DisruptorMqServiceImpl implements DisruptorMqService {
    @Autowired
    private RingBuffer
messageModelRingBuffer;
    @Override
    public void sayHelloMq(String message) {
        log.info("record the message: {}", message);
        long sequence = messageModelRingBuffer.next();
        try {
            MessageModel event = messageModelRingBuffer.get(sequence);
            event.setMessage(message);
            log.info("Add message to queue: {}", event);
        } catch (Exception e) {
            log.error("failed to add event", e);
        } finally {
            messageModelRingBuffer.publish(sequence);
        }
    }
}

8. Test class

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
public class DemoApplicationTests {
    @Autowired
    private DisruptorMqService disruptorMqService;
    @Test
    public void sayHelloMqTest() throws Exception {
        disruptorMqService.sayHelloMq("Message arrived, Hello world!");
        log.info("Message queue sent");
        Thread.sleep(2000); // wait for async consumer
    }
}

Test output shows the producer logging the message, the consumer receiving and processing it, and the overall flow completing without errors.

Conclusion

The producer‑consumer pattern is common, and many message‑queue solutions can achieve similar results, but Disruptor implements it entirely in memory with a lock‑free design, which explains its exceptional performance.

Javabackend developmentMessage QueueDisruptorConcurrent ProgrammingRing Buffer
Architect's Tech Stack
Written by

Architect's Tech Stack

Java backend, microservices, distributed systems, containerized programming, and more.

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.