✅ wait(), notify(), notifyAll()の正しい使い方

Java

~スレッド間通信を完全に理解しよう!~

はじめに:なぜこの3つを理解すべきなのか?

Java のマルチスレッドを学ぶと、必ず出てくるのが
wait()notify()notifyAll() という3つのメソッドです。

「なんとなくスレッドを止めたり再開したりするもの」
と理解している方も多いですが、実はこの3つの使い方を誤ると
プログラムが止まる・動かない・データ破損が起きるなどの問題が発生します。

逆に、正しく理解すれば次のようなメリットがあります。

  • 並行処理を安全に扱える
  • 他の言語のスレッド制御も理解できる
  • Java の「モニター機構」の本質が分かる

特に、スレッド間通信の理解は中級者への大きなステップアップです。


基本の理解:Java のスレッドとモニター機構

Java のすべてのオブジェクトは「モニター(monitor)」というロック機構を持っています。
wait()notify()notifyAll() はこのモニターと深く関係しています。

これらは Object クラス に定義されており、すべてのオブジェクトで利用可能です。

public final void wait() throws InterruptedException
public final void notify()
public final void notifyAll()

ただし、これらは必ず synchronized ブロックの中で呼ぶ必要があります。
モニターを取得していない状態で呼び出すと、IllegalMonitorStateException が発生します。


wait() の正しい使い方

wait() は、「スレッドを一時停止してロックを解放する」ためのメソッドです。

基本形

synchronized (obj) {
    while (!条件) {
        obj.wait(); // 条件が満たされるまで待機
    }
    // 条件が満たされたら処理を再開
}

ここでの重要ポイント:

  • if ではなく while を使う
    → 「スプリアスウェイクアップ(意図せぬ再開)」に備えるため。
  • 条件を再確認すること
    → 条件が偽のまま再開することを防ぐ。

notify() の正しい使い方

notify() は、「待機中のスレッドを1つだけ再開させる」メソッドです。

synchronized (obj) {
    // 状態を更新
    obj.notify(); // 1つのスレッドを起こす
}

ポイント:

  • 必ず synchronized ブロック内で呼ぶ
  • 起こされるスレッドはランダム(選べない)
  • 起こされたスレッドは、ロックを再取得するまで待つ

したがって、複数スレッドが待機している場合、どれが起きるかは保証されません。


notifyAll() の正しい使い方

notifyAll() は、待機しているすべてのスレッドを再開させるメソッドです。

synchronized (obj) {
    // 状態を更新
    obj.notifyAll(); // 全スレッドを起こす
}

全スレッドが一斉に実行されるわけではなく、
ロックを再取得できたスレッドから順番に実行されます。

使い分けの目安

メソッド起こすスレッド数適用シーン
notify()1つ1対1の通信(例:1つの生産者と1つの消費者)
notifyAll()全員複数スレッドが待機している場合

よくある間違いと注意点

❌ wait() を synchronized 外で呼ぶ

obj.wait(); // 例外発生!

✅ 正しくはこう

synchronized (obj) {
    obj.wait();
}

❌ if を使って条件判定する

if (!条件) {
    obj.wait();
}

✅ while で条件を再確認

while (!条件) {
    obj.wait();
}

❌ 状態を変えずに notify() する

状態を更新してから notify() を呼ばないと、
起こされたスレッドがすぐまた待機に戻ってしまいます。


実践例:プロデューサー・コンシューマー問題

class SharedQueue {
    private final List<Integer> queue = new ArrayList<>();
    private final int LIMIT = 5;

    public synchronized void produce(int value) throws InterruptedException {
        while (queue.size() == LIMIT) {
            wait(); // 満杯なら待機
        }
        queue.add(value);
        System.out.println("生産: " + value);
        notifyAll(); // 消費者に知らせる
    }

    public synchronized int consume() throws InterruptedException {
        while (queue.isEmpty()) {
            wait(); // 空なら待機
        }
        int value = queue.remove(0);
        System.out.println("消費: " + value);
        notifyAll(); // 生産者に知らせる
        return value;
    }
}

このコードは典型的な「生産者・消費者問題」を解決しています。

  • 生産者 → 満杯なら wait()
  • 消費者 → 空なら wait()
  • どちらかが動くたびに notifyAll() で相手を起こす

シンプルながら、これがスレッド間通信の本質です。


代替手段との比較

最近では、より安全で使いやすい仕組みが java.util.concurrent に用意されています。

  • ReentrantLockCondition
  • BlockingQueue
  • Semaphore
  • CountDownLatch

これらを使うと、バグを減らしつつ効率的なスレッド制御が可能です。
ただし、wait/notify の仕組みを理解していないと、これらの本質は理解できません。


まとめ

  • wait() はスレッドを待機させ、ロックを解放する
  • notify() は1つのスレッドを再開させる
  • notifyAll() は全スレッドを再開させる
  • すべて synchronized 内で呼ぶ必要がある
  • 条件確認は while を使う
  • 状態を更新してから通知する

これらを正しく理解できれば、マルチスレッドはもう怖くありません。


Javaプログラマーを目指すあなたへ

スレッド制御は、Java学習の中でも難関の一つです。
理解できたつもりでも、実際にコードを書くと「なぜ動かない?」と悩む方は多いです。

そんなときは、まず 絶対にJavaプログラマーになりたい人へ。 を読んでみてください。
この書籍は、Javaの基本から実践まで体系的に理解できるよう構成されています。

そして、
「コードレビューをしてもらいたい」
「転職を見据えて実践的なスキルを学びたい」
という方は、サイゼントアカデミー がおすすめです。

絶対にJava で学んだ基礎を、サイゼントアカデミー で実践に変える。
この2ステップで、あなたも確実に「現場で通用するJavaプログラマー」になれます。


これが、
「wait(), notify(), notifyAll()」を正しく理解するための完全ガイドです。
あなたのJavaスキルが、ここから一段上のレベルへ進むことを願っています。

コメント

タイトルとURLをコピーしました