Javaにおけるバルキングパターン:Javaの実行をスマートに制御
バルキングデザインパターンの目的
Javaにおけるバルキングパターンは、オブジェクトが不完全または不適切な状態にある場合に、特定のコードの実行を防止する並行処理デザインパターンです。このパターンは、マルチスレッドJavaアプリケーションにおける状態と並行処理の管理に不可欠です。
実際の例を用いたバルキングパターンの詳細な説明
現実世界の例
バルキングデザインパターンの現実世界のアナロジーは、コインランドリーの洗濯サービスに見ることができます。コインランドリーの洗濯機は、ドアが適切に閉じてロックされている場合にのみ洗濯を開始すると想像してください。ユーザーがドアが開いた状態で洗濯機を起動しようとすると、洗濯機は拒否して何も行いません。これにより、洗濯プロセスが安全に行える場合にのみ開始され、水のこぼれや洗濯機の潜在的な損傷を防ぎます。同様に、ソフトウェア設計におけるバルキングパターンは、操作がオブジェクトが適切な状態にある場合にのみ実行されるようにし、誤ったアクションを防ぎ、システムの安定性を維持します。
平易な言葉で言うと
バルキングパターンを使用すると、特定のコードは、オブジェクトが特定の状態にある場合にのみ実行されます。
Wikipediaによると
バルキングパターンは、オブジェクトが特定の状態にある場合にのみ、オブジェクトに対してアクションを実行するソフトウェアデザインパターンです。たとえば、オブジェクトがZIPファイルを読み取り、ZIPファイルが開いていないときに呼び出し元のメソッドがオブジェクトでgetメソッドを呼び出すと、オブジェクトは要求を「拒否」します。
Javaにおけるバルキングパターンのプログラム例
この例は、マルチスレッドJavaアプリケーションでのバルキングパターンを示しており、状態管理と並行処理制御を強調しています。バルキングパターンは、洗濯機がアイドル状態の場合にのみ洗濯を開始する洗濯機のスタートボタンで例示されています。これにより、状態管理が保証され、並行処理の問題を防ぎます。
洗濯機には、洗濯を開始するためのスタートボタンがあります。洗濯機が非アクティブの場合、ボタンは期待どおりに機能しますが、すでに洗濯中の場合はボタンは何も行いません。
この実装例では、WashingMachine
は、ENABLEDとWASHINGの2つの状態を持つことができるオブジェクトです。洗濯機がENABLEDの場合、スレッドセーフなメソッドを使用して状態がWASHINGに変わります。一方、すでに洗濯中で、他のスレッドがwash
を実行した場合、何も実行せずに戻ります。
WashingMachine
クラスの関連部分を次に示します。
@Slf4j
public class WashingMachine {
private final DelayProvider delayProvider;
private WashingMachineState washingMachineState;
public WashingMachine(DelayProvider delayProvider) {
this.delayProvider = delayProvider;
this.washingMachineState = WashingMachineState.ENABLED;
}
public WashingMachineState getWashingMachineState() {
return washingMachineState;
}
public void wash() {
synchronized (this) {
var machineState = getWashingMachineState();
LOGGER.info("{}: Actual machine state: {}", Thread.currentThread().getName(), machineState);
if (this.washingMachineState == WashingMachineState.WASHING) {
LOGGER.error("Cannot wash if the machine has been already washing!");
return;
}
this.washingMachineState = WashingMachineState.WASHING;
}
LOGGER.info("{}: Doing the washing", Thread.currentThread().getName());
this.delayProvider.executeAfterDelay(50, TimeUnit.MILLISECONDS, this::endOfWashing);
}
public synchronized void endOfWashing() {
washingMachineState = WashingMachineState.ENABLED;
LOGGER.info("{}: Washing completed.", Thread.currentThread().getId());
}
}
WashingMachine
で使用される単純なDelayProvider
インターフェースを次に示します。
public interface DelayProvider {
void executeAfterDelay(long interval, TimeUnit timeUnit, Runnable task);
}
次に、WashingMachine
を使用するアプリケーションを紹介します。
public static void main(String... args) {
final var washingMachine = new WashingMachine();
var executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 3; i++) {
executorService.execute(washingMachine::wash);
}
executorService.shutdown();
try {
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
executorService.shutdownNow();
}
} catch (InterruptedException ie) {
LOGGER.error("ERROR: Waiting on executor service shutdown!");
Thread.currentThread().interrupt();
}
}
プログラムのコンソール出力を次に示します。
14:02:52.268 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-2: Actual machine state: ENABLED
14:02:52.272 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-2: Doing the washing
14:02:52.272 [pool-1-thread-3] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-3: Actual machine state: WASHING
14:02:52.273 [pool-1-thread-3] ERROR com.iluwatar.balking.WashingMachine - Cannot wash if the machine has been already washing!
14:02:52.273 [pool-1-thread-1] INFO com.iluwatar.balking.WashingMachine - pool-1-thread-1: Actual machine state: WASHING
14:02:52.273 [pool-1-thread-1] ERROR com.iluwatar.balking.WashingMachine - Cannot wash if the machine has been already washing!
14:02:52.324 [pool-1-thread-2] INFO com.iluwatar.balking.WashingMachine - 14: Washing completed.
Javaでバルキングパターンを使用するタイミング
次の場合にバルキングパターンを使用します
- オブジェクトが特定の状態にある場合にのみ、オブジェクトに対してアクションを呼び出したい場合
- オブジェクトは通常、一時的に、ただし不明な時間だけバルキングしやすい状態にある場合
- 特定の条件が満たされた場合にのみ特定のアクションを進める必要があり、それらの条件が外部要因または同時操作のために時間の経過とともに変化することが予想されるマルチスレッドアプリケーションの場合。
Javaにおけるバルキングパターンの現実世界の応用
- リソースプーリング。リソースは、割り当てに有効な状態の場合にのみ割り当てられます。
- スレッド管理。スレッドは、特定の条件(タスクの可用性やリソースロックなど)が満たされた場合にのみタスクを続行します。
バルキングパターンの利点とトレードオフ
利点
- アクションを進めることができない状況での不要なロック取得を減らし、同時アプリケーションのパフォーマンスを向上させます。
- 状態管理と動作の明確な分離を促進し、よりクリーンなコードにつながります。
- 状態チェックで呼び出し元のコードを乱雑にすることなく、特定の条件下でのみ実行する必要がある操作の処理を簡素化します。
トレードオフ
- アクションが実行されるか無視されるかの条件を不明瞭にすることで、複雑さを招き、システムのデバッグと理解を困難にする可能性があります。
- 状態の変化が適切に監視されない場合、またはバルキング条件が厳しすぎる場合、機会の逸失やアクションの失敗につながる可能性があります。
関連するJavaデザインパターン
- 二重チェックロック:必要な場合にのみ初期化が発生し、オブジェクトの状態に基づいて条件付きでロジックを実行するという点で、バルキングに関連する不要なロックを回避します。
- ガード付きサスペンション:アクションがオブジェクトが特定の状態にある場合にのみ実行されるようにするという点で似ていますが、通常は状態が有効になるまで待機することが含まれます。
- 状態:状態パターンは、オブジェクトの状態と遷移を管理するためにバルキングと組み合わせて使用できます。