Javaにおけるガーディッドサスペンションパターン:クリティカルセクションにおける安全な並行性確保
別名
- 条件付きブロック
- 中断された実行
ガーディッドサスペンションデザインパターンの目的
ガーディッドサスペンションパターンは、ロックと条件の両方を必要とする操作を管理するための、Javaデザインパターンにおいて重要なパターンです。スレッドが適切な条件を効率的に待つことができるようにすることで、並行性制御を最適化します。
現実世界の例を用いたガーディッドサスペンションパターンの詳細な説明
現実世界の例
ガーディッドサスペンションパターンの実際的な例としては、ライドシェアサービスが挙げられます。このシステムでは、乗客は車が利用可能になるまで待機し、継続的なチェックなしで効率的なリソース利用を確保します。乗客が乗車リクエストを送信すると、ドライバーが利用可能になるまでリクエストは中断されます。システムはドライバーの可用性を監視し、ドライバーが新しい乗客を乗せる準備ができたら、待機中の乗客に通知し、乗車リクエストプロセスを再開します。これにより、乗客が利用可能なドライバーを継続的にチェックする必要がなくなり、ドライバーは可用性に基づいて乗客と効率的にマッチングされます。
分かりやすく言うと
ガーディッドサスペンションパターンは、あるスレッドが別のスレッドの実行結果を待つ場合に使用されます。
Wikipediaによると
並行プログラミングでは、ガーディッドサスペンションはロックと事前条件を必要とする操作を管理し、事前条件が満たされるまで実行を遅延させます。
Javaにおけるガーディッドサスペンションパターンのプログラム例
Javaの`GuardedQueue`クラスは、ガーディッドサスペンションパターンを使用した並行プログラミングを紹介します。スレッド管理と同期を管理する同期メソッドが含まれており、スレッドが実行するための適切な条件をどのように待機するかを示しています。
`GuardedQueue`クラスは、キューをカプセル化し、2つの同期メソッド`get`と`put`を提供することにより、ガーディッドサスペンションパターンを実証します。`get`メソッドはキューが空の場合は待機し、`put`メソッドはキューにアイテムを追加して待機中のスレッドに通知します。
@Slf4j
public class GuardedQueue {
private final Queue<Integer> sourceList = new LinkedList<>();
// Synchronized get method waits until the queue is not empty
public synchronized Integer get() {
while (sourceList.isEmpty()) {
try {
wait();
} catch (InterruptedException e) {
LOGGER.error("Error occurred: ", e);
}
}
return sourceList.poll();
}
// Synchronized put method adds an item to the queue and notifies waiting threads
public synchronized void put(Integer e) {
sourceList.add(e);
notify();
}
}
- `get`:このメソッドは、`sourceList`が空の間待機します。アイテムが追加され、`notify`が呼び出されると、待機中のスレッドはウェイクアップされて実行を継続し、アイテムを取得します。
- `put`:このメソッドは、キューにアイテムを追加し、`notify`を呼び出して、`get`メソッドで中断されている待機中のスレッドをウェイクアップします。
例を実行する`App`クラスは次のとおりです。
public class App {
public static void main(String[] args) {
GuardedQueue guardedQueue = new GuardedQueue();
ExecutorService executorService = Executors.newFixedThreadPool(3);
// Thread to get from the guardedQueue
executorService.execute(() -> {
Integer item = guardedQueue.get();
LOGGER.info("Retrieved: " + item);
});
// Simulating some delay before putting an item
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
LOGGER.error("Error occurred: ", e);
}
// Thread to put an item into the guardedQueue
executorService.execute(() -> {
guardedQueue.put(20);
LOGGER.info("Item added to queue");
});
executorService.shutdown();
try {
executorService.awaitTermination(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
LOGGER.error("Error occurred: ", e);
}
}
}
- `ExecutorService`は、スレッドプールを管理するために使用されます。
- 最初のスレッドは`GuardedQueue`からアイテムを取得しようとしますが、キューは最初は空であるため待機します。
- 2秒の遅延の後、2番目のスレッドがキューにアイテムを追加し、最初のスレッドをウェイクアップします。
- 最初のスレッドはアイテムを取得してログに記録します。
実行結果は次のとおりです。
19:22:58.984 [pool-1-thread-1] INFO com.iluwatar.guarded.suspension.GuardedQueue -- waiting
19:23:00.993 [pool-1-thread-2] INFO com.iluwatar.guarded.suspension.GuardedQueue -- putting
19:23:00.994 [pool-1-thread-2] INFO com.iluwatar.guarded.suspension.GuardedQueue -- notifying
19:23:00.994 [pool-1-thread-1] INFO com.iluwatar.guarded.suspension.GuardedQueue -- getting
19:23:00.994 [pool-1-thread-1] INFO com.iluwatar.guarded.suspension.GuardedQueue -- Retrieved: 20
- ログ出力は、イベントのシーケンスを示しています。最初のスレッドが待機し、2番目のスレッドがアイテムを配置し、最初のスレッドがアイテムを取得します。これは、ガーディッドサスペンションパターンが実際に動作していることを示しています。
Javaでガーディッドサスペンションパターンを使用する場合
このパターンは、スレッドが特定の条件を待つ必要があるシナリオに最適であり、効率的な並行性制御を促進し、ビジーウェイトのオーバーヘッドを削減します。
Javaにおけるガーディッドサスペンションパターンの実際のアプリケーション
- クライアントリクエストを待機するネットワークサーバー。
- コンシューマーがプロデューサーがデータを用意するのを待たなければならないプロデューサーコンシューマーシナリオ。
- 特定のイベントが発生した後にのみアクションがトリガーされるイベント駆動型アプリケーション。
ガーディッドサスペンションパターンの利点とトレードオフ
利点
- ビジーウェイトを防ぎ、CPU消費を削減します。
- 必要な条件またはリソースの可用性にアクションを同期させることにより、システムの応答性を向上させます。
トレードオフ
- 特に複数の条件を管理する必要がある場合、実装が複雑になります。
- 注意深く管理しないと、デッドロックが発生する可能性があります。
関連するJavaデザインパターン
- モニター:どちらのパターンも、条件に基づいてスレッドの同期を管理します。ガーディッドサスペンションは、条件が満たされるまでスレッドを中断することに特化していますが、モニターオブジェクトは条件と相互排除の処理をカプセル化します。
- プロデューサーコンシューマー:待機中のコンシューマーとプロデューサーを効率的に処理するために、多くの場合、ガーディッドサスペンションを使用して実装されます。
- バルキング:ガーディッドサスペンションと同様に、バルキングは、スレッドが条件をチェックし、条件が良好な場合にのみ進む場合に使用されます。そうでない場合は、すぐに返るか、処理を中止します。このパターンは、待機することなく、即時の条件チェックに基づいてアクションを管理することにより、ガーディッドサスペンションを補完します。