~スレッド間通信を完全に理解しよう!~
はじめに:なぜこの3つを理解すべきなのか?
Java のマルチスレッドを学ぶと、必ず出てくるのがwait()・notify()・notifyAll() という3つのメソッドです。
「なんとなくスレッドを止めたり再開したりするもの」
と理解している方も多いですが、実はこの3つの使い方を誤ると
プログラムが止まる・動かない・データ破損が起きるなどの問題が発生します。
逆に、正しく理解すれば次のようなメリットがあります。
- 並行処理を安全に扱える
- 他の言語のスレッド制御も理解できる
- Java の「モニター機構」の本質が分かる
特に、スレッド間通信の理解は中級者への大きなステップアップです。
基本の理解:Java のスレッドとモニター機構
Java のすべてのオブジェクトは「モニター(monitor)」というロック機構を持っています。wait()、notify()、notifyAll() はこのモニターと深く関係しています。
これらは Object クラス に定義されており、すべてのオブジェクトで利用可能です。
|
1 2 3 4 |
public final void wait() throws InterruptedException public final void notify() public final void notifyAll() |
ただし、これらは必ず synchronized ブロックの中で呼ぶ必要があります。
モニターを取得していない状態で呼び出すと、IllegalMonitorStateException が発生します。
wait() の正しい使い方
wait() は、「スレッドを一時停止してロックを解放する」ためのメソッドです。
基本形
|
1 2 3 4 5 6 7 |
synchronized (obj) { while (!条件) { obj.wait(); // 条件が満たされるまで待機 } // 条件が満たされたら処理を再開 } |
ここでの重要ポイント:
ifではなくwhileを使う
→ 「スプリアスウェイクアップ(意図せぬ再開)」に備えるため。- 条件を再確認すること
→ 条件が偽のまま再開することを防ぐ。
notify() の正しい使い方
notify() は、「待機中のスレッドを1つだけ再開させる」メソッドです。
|
1 2 3 4 5 |
synchronized (obj) { // 状態を更新 obj.notify(); // 1つのスレッドを起こす } |
ポイント:
- 必ず
synchronizedブロック内で呼ぶ - 起こされるスレッドはランダム(選べない)
- 起こされたスレッドは、ロックを再取得するまで待つ
したがって、複数スレッドが待機している場合、どれが起きるかは保証されません。
notifyAll() の正しい使い方
notifyAll() は、待機しているすべてのスレッドを再開させるメソッドです。
|
1 2 3 4 5 |
synchronized (obj) { // 状態を更新 obj.notifyAll(); // 全スレッドを起こす } |
全スレッドが一斉に実行されるわけではなく、
ロックを再取得できたスレッドから順番に実行されます。
使い分けの目安
| メソッド | 起こすスレッド数 | 適用シーン |
|---|---|---|
notify() | 1つ | 1対1の通信(例:1つの生産者と1つの消費者) |
notifyAll() | 全員 | 複数スレッドが待機している場合 |
よくある間違いと注意点
❌ wait() を synchronized 外で呼ぶ
|
1 2 |
obj.wait(); // 例外発生! |
✅ 正しくはこう
|
1 2 3 4 |
synchronized (obj) { obj.wait(); } |
❌ if を使って条件判定する
|
1 2 3 4 |
if (!条件) { obj.wait(); } |
✅ while で条件を再確認
|
1 2 3 4 |
while (!条件) { obj.wait(); } |
❌ 状態を変えずに notify() する
状態を更新してから notify() を呼ばないと、
起こされたスレッドがすぐまた待機に戻ってしまいます。
実践例:プロデューサー・コンシューマー問題
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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 に用意されています。
ReentrantLockとConditionBlockingQueueSemaphoreCountDownLatch
これらを使うと、バグを減らしつつ効率的なスレッド制御が可能です。
ただし、wait/notify の仕組みを理解していないと、これらの本質は理解できません。
まとめ
wait()はスレッドを待機させ、ロックを解放するnotify()は1つのスレッドを再開させるnotifyAll()は全スレッドを再開させる- すべて
synchronized内で呼ぶ必要がある - 条件確認は
whileを使う - 状態を更新してから通知する
これらを正しく理解できれば、マルチスレッドはもう怖くありません。
Javaプログラマーを目指すあなたへ
スレッド制御は、Java学習の中でも難関の一つです。
理解できたつもりでも、実際にコードを書くと「なぜ動かない?」と悩む方は多いです。
そんなときは、まず 絶対にJavaプログラマーになりたい人へ。 を読んでみてください。
この書籍は、Javaの基本から実践まで体系的に理解できるよう構成されています。
そして、
「コードレビューをしてもらいたい」
「転職を見据えて実践的なスキルを学びたい」
という方は、サイゼントアカデミー がおすすめです。
絶対にJava で学んだ基礎を、サイゼントアカデミー で実践に変える。
この2ステップで、あなたも確実に「現場で通用するJavaプログラマー」になれます。
これが、
「wait(), notify(), notifyAll()」を正しく理解するための完全ガイドです。
あなたのJavaスキルが、ここから一段上のレベルへ進むことを願っています。

コメント