Как реализовать паттерн producer/consumer?

Как реализовать паттерн producer/consumer?
Шаблон producer/consumer (производитель/потребитель) – простая и базовая реализация обмена данными между несколькими потоками. Поток-производитель отправляет объекты на условную обработку, потоки-потребители асинхронно принимают и обрабатывают их.

Общий вид решения выглядит так. Продюсер отправляет объекты в специальную коллекцию – буфер. Когда потребитель освобождается, он отправляет запрос на извлечение одного объекта из буфера. Если буфер пуст, потребитель блокируется и ждет, если буфер переполнен – ждет производитель.

На практике реализовать этот паттерн можно множеством способов. Самый правильный способ для применения в бою – использовать готовую реализацию из стандартной библиотеки, объект типа BlockingQueue.

На собеседовании обычно просят реализовать паттерн с нуля. Реализация представлена на изображении ниже. Модификатор synchronized делает так, чтобы в каждый момент времени мог выполняться только один из методов, и только одним потоком. Этого достаточно для корректной работы пока буфер не пуст и не полон. При пустом или полном буфере управление явно перебрасывается на производителя или потребителя соответственно, с помощью методов notify() и wait().

Шаблону producer/consumer посвящена глава 5.3 книги Java Concurrency in Practice.

Сильно упрощая, на основе этого паттерна работают сервисы-брокеры сообщений: Rabbit MQ, Apache ActiveMQ и другие.