Javaにおける生産者-消費者パターン:生産と消費プロセスの効率化
別名
- 有界バッファ
- 消費者-生産者
生産者-消費者デザインパターンの目的
並列Javaアプリケーションの重要なコンポーネントである生産者-消費者デザインパターンは、データの生産と消費のタスクを分離するために使用され、生産者がデータを生成し、消費者が互いに直接依存することなく同時にそのデータを処理できるようにします。
現実世界の例を用いた生産者-消費者パターンの詳細な説明
現実世界の例
典型的な自動車製造の設定では、生産者-消費者パターンは同期操作を促進し、効率的な組み立てと設置プロセスを保証します。生産のさまざまな段階が発生する自動車製造工場を想像してみてください。「生産者」は車のエンジンを組み立てるステーションであり、「消費者」はエンジンを車体に設置するステーションである可能性があります。エンジンは組み立てられるとコンベヤーベルト(バッファとして機能)に配置されます。設置ステーションは、エンジンを車に取り付けるためにコンベヤーベルトからエンジンを取り外します。これにより、エンジンの組み立てプロセスとエンジンの設置プロセスは独立して動作し、コンベヤーベルトはこれら2つの段階間の同期を管理します。組み立てステーションが設置ステーションが設置できるよりも速くエンジンを生産する場合、余剰エンジンは一時的にコンベヤーベルトに保管されます。逆に、設置ステーションがエンジンを必要とするが、組み立てステーションが一時的に停止している場合でも、ベルトで利用可能なエンジンに取り組むことができます。
簡単な言葉で
異なるレートで実行されている複数のループ間でデータを共有する方法を提供します。
Wikipediaによると
ダイクストラはこのケースについて次のように書いています。「私たちは、それぞれ「生産者」と「消費者」と呼ばれる2つのプロセスを検討します。生産者は、消費者によって処理される必要がある特定の情報の部分を生産する循環プロセスです。消費者はまた、生産者によって生産されたように、情報の次の部分を処理する必要がある循環プロセスです。この目的のために、2つのプロセスが無制限の容量のバッファを介して接続されていると仮定します。」
Javaにおける生産者-消費者パターンのプログラム例
アイテムの製造プロセスを考えてみましょう。生産者は、製造パイプラインがいっぱいになったときに生産を一時停止する必要があり、消費者は、製造パイプラインが空になったときにアイテムの消費を一時停止する必要があります。一緒に動作し、別々の時間に一時停止する生産と消費のプロセスを分離できます。
このJavaの例では、`Producer`は`ItemQueue`に格納されているアイテムを生成し、高性能Javaアプリケーションに不可欠な効率的なスレッド管理とデータ同期を実証します。
単純な`Item`レコードがあります。それらは`ItemQueue`に格納されます。
public record Item(String producer, int id) {}
public class ItemQueue {
private final BlockingQueue<Item> queue;
public ItemQueue() {
queue = new LinkedBlockingQueue<>(5);
}
public void put(Item item) throws InterruptedException {
queue.put(item);
}
public Item take() throws InterruptedException {
return queue.take();
}
}
`Producer`は`ItemQueue`にアイテムを生成します。
public class Producer {
private static final SecureRandom RANDOM = new SecureRandom();
private final ItemQueue queue;
private final String name;
private int itemId;
public Producer(String name, ItemQueue queue) {
this.name = name;
this.queue = queue;
}
public void produce() throws InterruptedException {
var item = new Item(name, itemId++);
queue.put(item);
Thread.sleep(RANDOM.nextInt(2000));
}
}
次に、`ItemQueue`からアイテムを取得する`Consumer`クラスがあります。
@Slf4j
public class Consumer {
private final ItemQueue queue;
private final String name;
public Consumer(String name, ItemQueue queue) {
this.name = name;
this.queue = queue;
}
public void consume() throws InterruptedException {
var item = queue.take();
LOGGER.info("Consumer [{}] consume item [{}] produced by [{}]", name,
item.getId(), item.getProducer());
}
}
さて、製造パイプライン中に、`Producer`クラスと`Consumer`クラスの両方からオブジェクトをインスタンス化できます。これらはキューからアイテムを生成および消費するためです。
public static void main(String[] args) {
var queue = new ItemQueue();
var executorService = Executors.newFixedThreadPool(5);
for (var i = 0; i < 2; i++) {
final var producer = new Producer("Producer_" + i, queue);
executorService.submit(() -> {
while (true) {
producer.produce();
}
});
}
for (var i = 0; i < 3; i++) {
final var consumer = new Consumer("Consumer_" + i, queue);
executorService.submit(() -> {
while (true) {
consumer.consume();
}
});
}
executorService.shutdown();
try {
executorService.awaitTermination(10, TimeUnit.SECONDS);
executorService.shutdownNow();
} catch (InterruptedException e) {
LOGGER.error("Error waiting for ExecutorService shutdown");
}
}
プログラム出力
08:10:08.008 [pool-1-thread-3] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_0] consume item [0] produced by [Producer_1]
08:10:08.008 [pool-1-thread-4] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_1] consume item [0] produced by [Producer_0]
08:10:08.517 [pool-1-thread-5] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_2] consume item [1] produced by [Producer_0]
08:10:08.952 [pool-1-thread-3] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_0] consume item [1] produced by [Producer_1]
08:10:09.208 [pool-1-thread-4] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_1] consume item [2] produced by [Producer_0]
08:10:09.354 [pool-1-thread-5] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_2] consume item [2] produced by [Producer_1]
08:10:10.214 [pool-1-thread-3] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_0] consume item [3] produced by [Producer_1]
08:10:10.585 [pool-1-thread-4] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_1] consume item [3] produced by [Producer_0]
08:10:11.530 [pool-1-thread-5] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_2] consume item [4] produced by [Producer_1]
08:10:11.682 [pool-1-thread-3] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_0] consume item [4] produced by [Producer_0]
08:10:11.781 [pool-1-thread-4] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_1] consume item [5] produced by [Producer_0]
08:10:12.209 [pool-1-thread-5] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_2] consume item [5] produced by [Producer_1]
08:10:13.045 [pool-1-thread-3] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_0] consume item [6] produced by [Producer_0]
08:10:13.861 [pool-1-thread-4] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_1] consume item [6] produced by [Producer_1]
08:10:14.739 [pool-1-thread-5] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_2] consume item [7] produced by [Producer_1]
08:10:14.842 [pool-1-thread-3] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_0] consume item [7] produced by [Producer_0]
08:10:15.975 [pool-1-thread-4] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_1] consume item [8] produced by [Producer_0]
08:10:16.378 [pool-1-thread-5] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_2] consume item [8] produced by [Producer_1]
08:10:16.967 [pool-1-thread-3] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_0] consume item [9] produced by [Producer_0]
08:10:17.417 [pool-1-thread-4] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_1] consume item [9] produced by [Producer_1]
08:10:17.483 [pool-1-thread-5] INFO com.iluwatar.producer.consumer.Consumer -- Consumer [Consumer_2] consume item [10] produced by [Producer_1]
現実世界の例を用いた生産者-消費者パターンの詳細な説明

Javaで生産者-消費者パターンを使用する場合
- 生産者がデータを追加し、消費者がデータを取得するバッファまたはキューを管理する必要がある場合、多くの場合、マルチスレッド環境で使用されます。
- データの生産と消費の分離が、アプリケーションの設計、パフォーマンス、または保守性に役立つ場合。
- 共有リソースまたはデータ構造への同期アクセスが必要なシナリオに適しています。
Javaにおける生産者-消費者パターンの実際のアプリケーション
- ワーカースレッドが別のスレッドによって生成されたタスクを処理するコンシューマーとして機能するスレッドプール。
- ログメッセージがアプリケーションのさまざまな部分によって生成され、ロギングサービスによって消費されるロギングフレームワーク。
- サービス間の非同期通信のための分散システムのメッセージキュー。
生産者-消費者パターンの利点とトレードオフ
利点
- 分離:生産者と消費者は独立して動作できるため、システム設計が簡素化されます。
- パフォーマンスの向上:複数の生産者スレッドと消費者スレッドを同時に動作させることができ、スループットが向上します。
- 柔軟性:既存のシステムに大きな変更を加えることなく、より多くの生産者または消費者を簡単に追加できます。
トレードオフ
- 複雑さ:同期と潜在的なデッドロックを慎重に処理する必要があります。
- リソース管理:オーバーフローまたはアンダーフロー状態を回避するために、バッファサイズを適切に管理します。
関連するJavaデザインパターン
- オブザーバー:どちらもイベントの通知または処理を扱いますが、オブザーバーパターンはイベントのサブスクリプションと通知に関するものであり、生産者-消費者は分離されたデータの生産と消費に焦点を当てています。
- スレッドプール:タスクがワーカースレッドのプールによって生成および消費される同様の分離アプローチを使用します。