スレッド

Java – Conditionクラスの使い方 – Lockを併用したスレッドの待機

JavaのConditionクラスは、Lockと併用してスレッド間の待機や通知を制御するために使用されます。

LocknewCondition()メソッドConditionオブジェクトを生成し、await()でスレッドを待機、signal()またはsignalAll()で待機中のスレッドを再開します。

これにより、synchronizedwait/notifyの代替として、より柔軟なスレッド制御が可能です。

Conditionクラスとは

JavaのConditionクラスは、スレッド間の通信を行うための機能を提供します。

特に、スレッドが特定の条件を満たすまで待機したり、他のスレッドに通知を送る際に使用されます。

Conditionは、Lockインターフェースと組み合わせて使用されることが一般的で、より柔軟なスレッド制御を可能にします。

Conditionクラスの主な機能

  • 待機: スレッドが特定の条件を満たすまで待機することができます。
  • 通知: 条件が満たされた際に、待機中のスレッドに通知を送ることができます。
  • 複数の条件: 複数の条件を管理することができ、より複雑なスレッド間の通信が可能です。

以下は、Conditionクラスを使用した簡単な例です。

この例では、スレッドが特定の条件を満たすまで待機し、条件が満たされた際に通知を受け取ります。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class App {
    private static final Lock lock = new ReentrantLock(); // Lockのインスタンスを作成
    private static final Condition condition = lock.newCondition(); // Conditionのインスタンスを作成
    private static boolean conditionMet = false; // 条件が満たされたかどうかのフラグ
    public static void main(String[] args) throws InterruptedException {
        Thread waitingThread = new Thread(() -> {
            lock.lock(); // Lockを取得
            try {
                while (!conditionMet) { // 条件が満たされるまで待機
                    condition.await(); // 待機
                }
                System.out.println("条件が満たされました!"); // 条件が満たされた場合の処理
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // 割り込み処理
            } finally {
                lock.unlock(); // Lockを解放
            }
        });
        Thread notifyingThread = new Thread(() -> {
            lock.lock(); // Lockを取得
            try {
                Thread.sleep(2000); // 2秒待機
                conditionMet = true; // 条件を満たす
                condition.signal(); // 待機中のスレッドに通知
                System.out.println("通知を送信しました!"); // 通知送信のメッセージ
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // 割り込み処理
            } finally {
                lock.unlock(); // Lockを解放
            }
        });
        waitingThread.start(); // 待機スレッドを開始
        notifyingThread.start(); // 通知スレッドを開始
        waitingThread.join(); // 待機スレッドの終了を待つ
        notifyingThread.join(); // 通知スレッドの終了を待つ
    }
}
通知を送信しました!
条件が満たされました!

この例では、waitingThreadが条件が満たされるまで待機し、notifyingThreadが2秒後に条件を満たして通知を送信します。

これにより、スレッド間の協調動作が実現されます。

LockとConditionの基本的な使い方

JavaのLockConditionは、スレッド間の同期を管理するための強力なツールです。

Lockは、スレッドが共有リソースにアクセスする際の排他制御を提供し、Conditionは、スレッドが特定の条件を満たすまで待機したり、他のスレッドに通知を送るためのメカニズムを提供します。

以下に、基本的な使い方を解説します。

Lockの基本的な使い方

  • Lockの取得: lock.lock()メソッドを使用して、ロックを取得します。
  • ロックの解放: 処理が終わったら、lock.unlock()メソッドを使用してロックを解放します。
  • 例外処理: ロックの取得と解放は、try-finallyブロックを使用して行うことが推奨されます。

Conditionの基本的な使い方

  • Conditionの作成: LockインスタンスからnewCondition()メソッドを使用してConditionオブジェクトを作成します。
  • 待機: condition.await()メソッドを使用して、スレッドを待機状態にします。
  • 通知: condition.signal()またはcondition.signalAll()メソッドを使用して、待機中のスレッドに通知を送ります。

以下は、LockConditionを使用した基本的な例です。

この例では、スレッドが条件を満たすまで待機し、条件が満たされた際に通知を受け取ります。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class App {
    private static final Lock lock = new ReentrantLock(); // Lockのインスタンスを作成
    private static final Condition condition = lock.newCondition(); // Conditionのインスタンスを作成
    private static boolean conditionMet = false; // 条件が満たされたかどうかのフラグ
    public static void main(String[] args) throws InterruptedException {
        Thread waitingThread = new Thread(() -> {
            lock.lock(); // Lockを取得
            try {
                while (!conditionMet) { // 条件が満たされるまで待機
                    System.out.println("条件を待っています..."); // 待機中のメッセージ
                    condition.await(); // 待機
                }
                System.out.println("条件が満たされました!"); // 条件が満たされた場合の処理
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // 割り込み処理
            } finally {
                lock.unlock(); // Lockを解放
            }
        });
        Thread notifyingThread = new Thread(() -> {
            lock.lock(); // Lockを取得
            try {
                Thread.sleep(2000); // 2秒待機
                conditionMet = true; // 条件を満たす
                condition.signal(); // 待機中のスレッドに通知
                System.out.println("通知を送信しました!"); // 通知送信のメッセージ
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // 割り込み処理
            } finally {
                lock.unlock(); // Lockを解放
            }
        });
        waitingThread.start(); // 待機スレッドを開始
        notifyingThread.start(); // 通知スレッドを開始
        waitingThread.join(); // 待機スレッドの終了を待つ
        notifyingThread.join(); // 通知スレッドの終了を待つ
    }
}
条件を待っています...
条件を待っています...
通知を送信しました!
条件が満たされました!

この例では、waitingThreadが条件が満たされるまで待機し、notifyingThreadが2秒後に条件を満たして通知を送信します。

これにより、スレッド間の協調動作が実現されます。

LockConditionを組み合わせることで、より柔軟で効率的なスレッド制御が可能になります。

Conditionを使ったスレッドの待機と通知の実装

Conditionクラスを使用することで、スレッド間の待機と通知のメカニズムを簡単に実装できます。

これにより、特定の条件が満たされるまでスレッドを待機させ、条件が満たされた際に他のスレッドに通知を送ることが可能になります。

以下に、具体的な実装方法を解説します。

スレッドの待機と通知の流れ

  1. ロックの取得: スレッドが共有リソースにアクセスする前に、Lockを取得します。
  2. 条件の確認: 条件が満たされているかを確認します。
  3. 待機: 条件が満たされていない場合、condition.await()を呼び出してスレッドを待機状態にします。
  4. 条件の変更: 別のスレッドが条件を満たした場合、condition.signal()またはcondition.signalAll()を呼び出して待機中のスレッドに通知します。
  5. ロックの解放: 処理が終わったら、ロックを解放します。

以下は、Conditionを使用してスレッドの待機と通知を実装した例です。

この例では、1つのスレッドが条件を満たすまで待機し、もう1つのスレッドが条件を満たして通知を送信します。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class App {
    private static final Lock lock = new ReentrantLock(); // Lockのインスタンスを作成
    private static final Condition condition = lock.newCondition(); // Conditionのインスタンスを作成
    private static boolean conditionMet = false; // 条件が満たされたかどうかのフラグ
    public static void main(String[] args) throws InterruptedException {
        Thread waitingThread = new Thread(() -> {
            lock.lock(); // Lockを取得
            try {
                System.out.println("待機スレッドが条件を待っています..."); // 待機中のメッセージ
                while (!conditionMet) { // 条件が満たされるまで待機
                    condition.await(); // 待機
                }
                System.out.println("待機スレッド: 条件が満たされました!"); // 条件が満たされた場合の処理
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // 割り込み処理
            } finally {
                lock.unlock(); // Lockを解放
            }
        });
        Thread notifyingThread = new Thread(() -> {
            lock.lock(); // Lockを取得
            try {
                Thread.sleep(3000); // 3秒待機
                conditionMet = true; // 条件を満たす
                condition.signal(); // 待機中のスレッドに通知
                System.out.println("通知スレッド: 条件を満たしました!"); // 通知送信のメッセージ
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // 割り込み処理
            } finally {
                lock.unlock(); // Lockを解放
            }
        });
        waitingThread.start(); // 待機スレッドを開始
        notifyingThread.start(); // 通知スレッドを開始
        waitingThread.join(); // 待機スレッドの終了を待つ
        notifyingThread.join(); // 通知スレッドの終了を待つ
    }
}
待機スレッドが条件を待っています...
通知スレッド: 条件を満たしました!
待機スレッド: 条件が満たされました!

この例では、waitingThreadが条件が満たされるまで待機し、notifyingThreadが3秒後に条件を満たして通知を送信します。

これにより、スレッド間の協調動作が実現され、Conditionを使用することで、スレッドの待機と通知の実装が簡単に行えることが示されています。

Conditionクラスを使うメリットとデメリット

Conditionクラスは、Javaにおけるスレッド間の通信を効率的に行うための重要な機能ですが、使用する際にはメリットとデメリットを理解しておくことが重要です。

以下に、Conditionクラスを使用する際の主なメリットとデメリットをまとめます。

メリット

メリット説明
柔軟なスレッド制御Conditionを使用することで、複数の条件を管理し、スレッドの待機と通知を柔軟に制御できます。
効率的なリソース管理スレッドが条件を満たすまで待機するため、CPUリソースを無駄に消費せず、効率的にリソースを管理できます。
明示的な待機と通知await()signal()メソッドを使用することで、待機と通知の処理が明示的になり、コードの可読性が向上します。
デッドロックの回避Lockと組み合わせて使用することで、デッドロックを回避しやすくなります。try-finallyブロックを使用することで、ロックの解放を確実に行えます。

デメリット

デメリット説明
複雑な実装Conditionを使用することで、スレッドの待機と通知の実装が複雑になる場合があります。特に、複数の条件を扱う場合は注意が必要です。
パフォーマンスのオーバーヘッドLockConditionを使用することで、従来のsynchronizedブロックに比べてオーバーヘッドが増加する可能性があります。特に、頻繁にロックを取得・解放する場合は注意が必要です。
エラーハンドリングの必要性await()メソッドInterruptedExceptionをスローするため、適切なエラーハンドリングが必要です。これにより、コードが煩雑になることがあります。
スレッドの競合複数のスレッドが同時に条件を満たそうとする場合、競合が発生し、パフォーマンスが低下する可能性があります。

Conditionクラスは、スレッド間の通信を効率的に行うための強力なツールですが、使用する際にはそのメリットとデメリットを理解しておくことが重要です。

特に、複雑な実装やパフォーマンスのオーバーヘッドに注意しながら、適切に活用することで、より効果的なスレッド制御が可能になります。

Conditionクラスの応用例

Conditionクラスは、スレッド間の待機と通知を効率的に管理するための強力な機能を提供します。

ここでは、Conditionクラスの具体的な応用例をいくつか紹介します。

これにより、実際のプログラムでどのように活用できるかを理解することができます。

1. プロデューサー-コンシューマーパターン

プロデューサー-コンシューマーパターンは、データの生成と消費を行うスレッド間の協調動作を示す典型的な例です。

プロデューサーはデータを生成し、コンシューマーはそのデータを消費します。

Conditionを使用することで、バッファが空または満杯のときにスレッドを待機させることができます。

import java.util.LinkedList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class App {
    private static final int CAPACITY = 5; // バッファの最大容量
    private static final LinkedList<Integer> buffer = new LinkedList<>(); // バッファ
    private static final Lock lock = new ReentrantLock(); // Lockのインスタンス
    private static final Condition notFull = lock.newCondition(); // バッファが満杯でないことを示すCondition
    private static final Condition notEmpty = lock.newCondition(); // バッファが空でないことを示すCondition
    public static void main(String[] args) {
        Thread producer = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                lock.lock(); // Lockを取得
                try {
                    while (buffer.size() == CAPACITY) { // バッファが満杯の場合
                        notFull.await(); // 待機
                    }
                    buffer.add(i); // データをバッファに追加
                    System.out.println("プロデューサー: " + i + " を追加しました。");
                    notEmpty.signal(); // コンシューマーに通知
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); // 割り込み処理
                } finally {
                    lock.unlock(); // Lockを解放
                }
            }
        });
        Thread consumer = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                lock.lock(); // Lockを取得
                try {
                    while (buffer.isEmpty()) { // バッファが空の場合
                        notEmpty.await(); // 待機
                    }
                    int value = buffer.removeFirst(); // バッファからデータを取り出す
                    System.out.println("コンシューマー: " + value + " を消費しました。");
                    notFull.signal(); // プロデューサーに通知
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); // 割り込み処理
                } finally {
                    lock.unlock(); // Lockを解放
                }
            }
        });
        producer.start(); // プロデューサースレッドを開始
        consumer.start(); // コンシューマースレッドを開始
    }
}

2. スレッドのタイムアウト処理

Conditionクラスは、スレッドが特定の条件を満たすまで待機する際に、タイムアウトを設定することも可能です。

これにより、待機時間を制限し、無限に待機することを防ぐことができます。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class App {
    private static final Lock lock = new ReentrantLock(); // Lockのインスタンス
    private static final Condition condition = lock.newCondition(); // Conditionのインスタンス
    private static boolean conditionMet = false; // 条件が満たされたかどうかのフラグ
    public static void main(String[] args) throws InterruptedException {
        Thread waitingThread = new Thread(() -> {
            lock.lock(); // Lockを取得
            try {
                System.out.println("待機スレッドが条件を待っています...");
                if (!condition.await(5, java.util.concurrent.TimeUnit.SECONDS)) { // 5秒待機
                    System.out.println("待機タイムアウト!"); // タイムアウトメッセージ
                } else {
                    System.out.println("条件が満たされました!"); // 条件が満たされた場合の処理
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // 割り込み処理
            } finally {
                lock.unlock(); // Lockを解放
            }
        });
        waitingThread.start(); // 待機スレッドを開始
        waitingThread.join(); // 待機スレッドの終了を待つ
    }
}

3. バリア同期

バリア同期は、複数のスレッドが特定のポイントで待機し、全てのスレッドがそのポイントに到達したときに一斉に処理を再開する手法です。

Conditionを使用することで、バリアの実装が可能になります。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class App {
    private static final Lock lock = new ReentrantLock(); // Lockのインスタンス
    private static final Condition condition = lock.newCondition(); // Conditionのインスタンス
    private static int count = 0; // 到達したスレッドのカウント
    private static final int totalThreads = 3; // 総スレッド数
    public static void main(String[] args) {
        for (int i = 0; i < totalThreads; i++) {
            new Thread(() -> {
                lock.lock(); // Lockを取得
                try {
                    count++; // 到達したスレッドのカウントを増加
                    if (count == totalThreads) { // 全てのスレッドが到達した場合
                        condition.signalAll(); // 待機中のスレッドに通知
                    } else {
                        System.out.println("スレッドがバリアに到達しました。待機中...");
                        condition.await(); // 待機
                        System.out.println("スレッドが再開しました!"); // 再開メッセージ
                    }
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); // 割り込み処理
                } finally {
                    lock.unlock(); // Lockを解放
                }
            }).start(); // スレッドを開始
        }
    }
}

Conditionクラスは、スレッド間の待機と通知を効率的に管理するための強力なツールです。

プロデューサー-コンシューマーパターン、スレッドのタイムアウト処理、バリア同期など、さまざまなシナリオで活用できます。

これらの応用例を参考にすることで、実際のプログラムにおけるConditionクラスの使い方を理解し、効果的に活用できるようになるでしょう。

まとめ

この記事では、JavaのConditionクラスを使用したスレッド間の待機と通知のメカニズムについて詳しく解説しました。

Conditionクラスは、スレッドの協調動作を実現するための強力なツールであり、プロデューサー-コンシューマーパターンやバリア同期など、さまざまな応用が可能です。

これらの知識を活用して、実際のプログラムにおけるスレッド制御をより効果的に行うことを目指してみてください。

関連記事

Back to top button