Javaスレッドの基本状態とその特徴
Javaのスレッドはjava.lang.Thread.State列挙型で定義される6つの状態を持ち、それぞれが異なる実行フェーズを表しています。
| 状態名 | 説明 | 遷移条件 |
|---|---|---|
| CREATED | スレッドインスタンス生成後、start()メソッド呼び出し前 | new Thread(runnable) |
| RUNNING_READY | CPU時間割当待ちまたは実行中の状態 | thread.start()呼び出し後 |
| BLOCKED_ON_MONITOR | synchronizedロック取得待ちによるブロッキング状態 | 他スレッドがロック保持中のコード領域へのアクセス時 |
| WAITING_INDEFINITE | 明確な解除トリガーが必要な無期限待機状態 | Object.wait(), Thread.join()呼び出し時 |
| WAITING_TIMED | 時間制限付きの待機状態 | Thread.sleep(), Object.wait(timeout)呼び出し時 |
| FINISHED | 実行終了または例外発生による終了状態 | run()メソッド完了時 |
状態遷移の詳細メカニズム
各状態間の遷移は特定の条件下でのみ発生し、理解することで並列処理の問題解決に役立ちます。
- CREATED → RUNNING_READY: start()メソッド呼び出しによりシステムリソースが割り当てられ、実行可能状態に移行
- RUNNING_READY → BLOCKED_ON_MONITOR: synchronized保護されたリソースへのアクセス時に他スレッドのロック解放待ち
- RUNNING_READY → WAITING_INDEFINITE: wait()やjoin()メソッド呼び出しによる待機状態への移行
- RUNNING_READY → WAITING_TIMED: sleep()などの時間指定待機メソッド呼び出し
- BLOCKED_ON_MONITOR → RUNNING_READY: 目的のロックを取得した際に発生
- WAITING系 → RUNNING_READY: notify()や時間経過、join対象の完了などによって解除
重要な設計原則と落とし穴
状態遷移に関する重要な考慮事項:
- 一度終了したスレッドは再起動不可能
- WAITING状態からの解除はロック再取得プロセスを含む
- WAITINGとBLOCKEDの違いを正しく理解する必要がある
- 古くなったstop()メソッドは使用しない
実践的な診断手法とツール
実行中のアプリケーションでスレッド状態を確認するための方法:
// コード内での状態確認
Thread targetThread = new Thread(() -> {
// 処理内容
});
targetThread.start();
Thread.State currentState = targetThread.getState();
// コマンドラインでの診断
// jpsコマンドでPID確認
// jstack <pid>で全スレッド状態をダンプ
面接向けの応用質問と回答戦略
高度な理解を問う質問に対する回答例:
Q: notify()呼び出し後のスレッド動作について説明してください。
A: notify()によってWAITING状態のスレッドが即座に実行されるわけではありません。まずBLOCKED状態に移行し、以前wait()で解放したロックを再取得する必要があります。ロック獲得後にのみRUNNING_READY状態に戻り、実行可能になります。
Q: sleep()とwait()の主な違いは何ですか?
A: sleep()はロックを保持したまま時間待機し、wait()はロックを解放して待機します。また、wait()はsynchronizedブロック内でしか呼び出せません。
並列処理設計における考慮事項
スレッド状態の理解は以下の設計要素に影響します:
- リソース競合の検出と軽減
- パフォーマンスボトルネックの特定
- デッドロックの予防と検出
- 適切なスレッドプールサイズの決定
wait/notifyメカニズムの詳細理解
スレッド間協調のための基本的な同期プリミティブの仕組み:
| 役割 | メソッド | 目的 | 後続処理 |
|---|---|---|---|
| 待機側 | wait() |
条件不成立時の待機とロック解放 | 通知後ロック再取得、条件再評価 |
| 通知側 | notify() |
条件変更後の他スレッド起床 | ロック保持継続、同期ブロック完了まで |
これらのメソッドはすべてsynchronizedコンテキスト内で呼び出す必要があります。条件の検証にはifではなくwhileループを使用し、spurious wakeupに対応することが推奨されます。
実装例:プロデューサ・コンシューマパターン
public class ProducerConsumerExample {
private final Object monitor = new Object();
private final java.util.Queue<String> buffer = new java.util.LinkedList<>();
private final int capacity = 5;
public void produce(String item) throws InterruptedException {
synchronized (monitor) {
while (buffer.size() == capacity) {
monitor.wait(); // バッファフル時の待機
}
buffer.add(item);
System.out.println("Produced: " + item);
monitor.notifyAll(); // 待機中のコンシューマを通知
}
}
public String consume() throws InterruptedException {
synchronized (monitor) {
while (buffer.isEmpty()) {
monitor.wait(); // バッファ空時の待機
}
String item = buffer.poll();
System.out.println("Consumed: " + item);
monitor.notifyAll(); // 待機中のプロデューサを通知
return item;
}
}
}