Javaでのスレッドのライフサイクル
1. 序章
この記事では、Javaのコアコンセプトであるスレッドのライフサイクルについて詳しく説明します。
スレッド実行中のこれらの状態をよりよく理解するために、簡単な図解図と、もちろん実用的なコードスニペットを使用します。
Javaでのスレッドの理解を始めるには、スレッドの作成に関するこの記事から始めるのがよいでしょう。
2. Javaでのマルチスレッド
Java言語では、マルチスレッドはスレッドのコアコンセプトによって駆動されます。 ライフサイクル中に、スレッドはさまざまな状態を通過します。
3. Javaでのスレッドのライフサイクル
java.lang.Thread クラスには、潜在的な状態を定義する static State enum –が含まれています。 任意の時点で、スレッドは次のいずれかの状態になります。
- NEW –まだ実行を開始していない新しく作成されたスレッド
- 実行可能– 実行中または実行の準備ができていますが、リソースの割り当てを待機しています
- BLOCKED –同期されたブロック/メソッドに入るまたは再入るためのモニターロックを取得するのを待っています
- 待機中–他のスレッドが時間制限なしで特定のアクションを実行するのを待機しています
- TIMED_WAITING –他のスレッドが指定された期間特定のアクションを実行するのを待っています
- 終了–は実行を完了しました
これらの状態はすべて上の図でカバーされています。 次に、これらのそれぞれについて詳しく説明します。
3.1. 新しい
NEWスレッド(またはBorn Thread)は、作成されたがまだ開始されていないスレッドです。 start()メソッドを使用して開始するまで、この状態のままになります。
次のコードスニペットは、NEW状態にある新しく作成されたスレッドを示しています。
Runnable runnable = new NewState();
Thread t = new Thread(runnable);
Log.info(t.getState());
上記のスレッドを開始していないため、メソッド t.getState()は次のように出力します。
NEW
3.2. 実行可能
新しいスレッドを作成し、その上で start()メソッドを呼び出すと、NEWからRUNNABLE状態に移行します。 この状態のスレッドは実行中または実行準備ができていますが、システムからのリソース割り当てを待機しています。
マルチスレッド環境では、Thread-Scheduler(JVMの一部)は各スレッドに一定の時間を割り当てます。 そのため、特定の時間実行されてから、他のRUNNABLEスレッドに制御を放棄します。
たとえば、 t.start()メソッドを前のコードに追加して、現在の状態にアクセスしてみましょう。
Runnable runnable = new NewState();
Thread t = new Thread(runnable);
t.start();
Log.info(t.getState());
このコードは、最も可能性が高い出力を次のように返します。
RUNNABLE
この例では、コントロールが t.getState()に到達するまでに、RUNNABLE状態のままであることが常に保証されているわけではないことに注意してください。
Thread-Scheduler によってすぐにスケジュールされ、実行が終了する場合があります。 このような場合、異なる出力が得られる可能性があります。
3.3. ブロックされた
スレッドは、現在実行する資格がない場合、BLOCKED状態にあります。 モニターのロックを待機していて、他のスレッドによってロックされているコードのセクションにアクセスしようとすると、この状態になります。
この状態を再現してみましょう。
public class BlockedState {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new DemoThreadB());
Thread t2 = new Thread(new DemoThreadB());
t1.start();
t2.start();
Thread.sleep(1000);
Log.info(t2.getState());
System.exit(0);
}
}
class DemoThreadB implements Runnable {
@Override
public void run() {
commonResource();
}
public static synchronized void commonResource() {
while(true) {
// Infinite loop to mimic heavy processing
// 't1' won't leave this method
// when 't2' try to enter this
}
}
}
このコードでは:
- t1とt2の2つの異なるスレッドを作成しました
- t1 が開始し、同期された commonResource()メソッドに入ります。 これは、1つのスレッドのみがアクセスできることを意味します。 このメソッドにアクセスしようとする他のすべての後続のスレッドは、現在のスレッドが処理を終了するまで、それ以降の実行からブロックされます。
- t1 がこのメソッドに入ると、無限のwhileループに保持されます。 これは、他のすべてのスレッドがこのメソッドに入ることができないように、重い処理を模倣するためだけのものです。
- ここで、 t2 を開始すると、 commonResource()メソッドに入ろうとします。このメソッドは、 t1、、つまりt2によって既にアクセスされています。 ]はBLOCKED状態に保たれます
この状態で、 t2.getState()を呼び出し、次のように出力を取得します。
BLOCKED
3.4. 待っている
スレッドは、他のスレッドが特定のアクションを実行するのを待機しているときにWAITING状態になります。 JavaDocs によると、次の3つのメソッドのいずれかを呼び出すことで、どのスレッドもこの状態に入ることができます。 :
- object.wait()
- thread.join()または
- LockSupport.park()
wait()および join()では、タイムアウト期間を定義しないことに注意してください。このシナリオについては、次のセクションで説明します。
別のチュートリアルがあり、 wait()、 notify()、および notifyAll()の使用について詳しく説明しています。
今のところ、この状態を再現してみましょう。
public class WaitingState implements Runnable {
public static Thread t1;
public static void main(String[] args) {
t1 = new Thread(new WaitingState());
t1.start();
}
public void run() {
Thread t2 = new Thread(new DemoThreadWS());
t2.start();
try {
t2.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Log.error("Thread interrupted", e);
}
}
}
class DemoThreadWS implements Runnable {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Log.error("Thread interrupted", e);
}
Log.info(WaitingState.t1.getState());
}
}
ここで何をしているのかを話し合いましょう。
- t1を作成して開始しました
- t1はt2を作成し、それを開始します
- t2 の処理が続行されている間、 t2.join()を呼び出します。これにより、t1がWAITING状態になります。 は実行を終了しました
- t1はt2の完了を待機しているため、 t2からt1.getState()を呼び出しています。
ここでの出力は、ご想像のとおりです。
WAITING
3.5. 時限待機
スレッドは、別のスレッドが規定の時間内に特定のアクションを実行するのを待っているときに、TIMED_WAITING状態になります。
JavaDocs によると、スレッドをTIMED_WAITING状態にする方法は5つあります。
- thread.sleep(長いミリ秒)
- wait(int timeout)または wait(int timeout、int nanos)
- thread.join(long millis )
- LockSupport.parkNanos
- LockSupport.parkUntil
Javaでのwait()と sleep()の違いについて詳しくは、この専用記事をご覧ください。
今のところ、この状態をすばやく再現してみましょう。
public class TimedWaitingState {
public static void main(String[] args) throws InterruptedException {
DemoThread obj1 = new DemoThread();
Thread t1 = new Thread(obj1);
t1.start();
// The following sleep will give enough time for ThreadScheduler
// to start processing of thread t1
Thread.sleep(1000);
Log.info(t1.getState());
}
}
class DemoThread implements Runnable {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Log.error("Thread interrupted", e);
}
}
}
ここでは、5秒のタイムアウト期間でスリープ状態に入るスレッドt1を作成して開始しました。 出力は次のようになります。
TIMED_WAITING
3.6. 終了しました
これはデッドスレッドの状態です。 実行が終了したか、異常終了した場合はTERMINATED状態になります。
スレッドを停止するさまざまな方法について説明している専用の記事があります。
次の例でこの状態を実現してみましょう。
public class TerminatedState implements Runnable {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new TerminatedState());
t1.start();
// The following sleep method will give enough time for
// thread t1 to complete
Thread.sleep(1000);
Log.info(t1.getState());
}
@Override
public void run() {
// No processing in this block
}
}
ここで、スレッド t1 を開始している間、次のステートメント Thread.sleep(1000)は、 t1 が完了するのに十分な時間を与えるため、このプログラムは次のようになります。出力を次のように使用します。
TERMINATED
スレッドの状態に加えて、 isAlive()メソッドをチェックして、スレッドが生きているかどうかを判断できます。 たとえば、このスレッドで isAlive()メソッドを呼び出すと、次のようになります。
Assert.assertFalse(t1.isAlive());
戻ります
4. 結論
このチュートリアルでは、Javaのスレッドのライフサイクルについて学びました。 Thread.State 列挙型によって定義された6つの状態すべてを調べ、簡単な例でそれらを再現しました。
コードスニペットはほとんどすべてのマシンで同じ出力を提供しますが、一部の例外的なケースでは、スレッドスケジューラの正確な動作を判断できないため、いくつかの異なる出力が得られる場合があります。
また、いつものように、ここで使用されているコードスニペットはGitHubで入手できます。