Javaにおけるスレッドのライフサイクル

Javaのスレッドのライフサイクル

 

1. 前書き

この記事では、Javaのコアコンセプトであるスレッドのライフサイクルについて詳しく説明します。

簡単な図解図と、もちろん実用的なコードスニペットを使用して、スレッド実行中のこれらの状態をよりよく理解します。

Javaでスレッドを理解し始めるには、スレッドの作成に関するthis articleを開始することをお勧めします。

2. Javaでのマルチスレッド

In the Java language, multithreading is driven by the core concept of a Thread。 ライフサイクル中、スレッドはさまざまな状態を通過します。

image

3. Javaのスレッドのライフサイクル

java.lang.Threadクラスには、その潜在的な状態を定義するstatic State enum –が含まれています。 どの時点でも、スレッドは次のいずれかの状態になります。

  1. NEW –新しく作成されたスレッドで、まだ実行が開始されていません

  2. RUNNABLE –は実行中または実行の準備ができていますが、リソースの割り当てを待機しています

  3. 同期されたブロック/メソッドに入るまたは再入るためのモニターロックの取得を待機しているBLOCKED –

  4. WAITING –は、他のスレッドが時間制限なしで特定のアクションを実行するのを待っています

  5. TIMED_WAITING –は、他のスレッドが指定された期間、特定のアクションを実行するのを待機しています

  6. TERMINATED –は実行を完了しました

これらの状態はすべて上の図でカバーされています。次に、これらのそれぞれについて詳しく説明します。

3.1. New

A NEW Thread (or a Born Thread) is a thread that’s been created but not yet started.start()メソッドを使用して開始するまで、この状態のままになります。

次のコードスニペットは、NEW状態にある新しく作成されたスレッドを示しています。

Runnable runnable = new NewState();
Thread t = new Thread(runnable);
Log.info(t.getState());

上記のスレッドを開始していないため、メソッドt.getState()は次のように出力します。

NEW

3.2. ランナブル

新しいスレッドを作成し、そのスレッドでstart()メソッドを呼び出すと、スレッドはNEWからRUNNABLEの状態に移行します。 Threads in this state are either running or ready to run, but they’re waiting for resource allocation from the system.

マルチスレッド環境では、スレッドスケジューラ(JVMの一部)は各スレッドに一定の時間を割り当てます。 したがって、特定の時間実行されてから、他のRUNNABLEスレッドに制御を放棄します。

たとえば、前のコードにt.start()メソッドを追加して、現在の状態にアクセスしてみましょう。

Runnable runnable = new NewState();
Thread t = new Thread(runnable);
t.start();
Log.info(t.getState());

このコードは、出力を次のように返すmost likelyです。

RUNNABLE

この例では、コントロールがt.getState()に到達するまでに、コントロールがRUNNABLE状態のままであることが常に保証されているわけではないことに注意してください。

Thread-Schedulerによってすぐにスケジュールされ、実行が終了する場合があります。 このような場合、異なる出力が得られる場合があります。

3.3. ブロックされました

スレッドは、現在実行する資格がない場合、BLOCKED状態にあります。 It enters this state when it is waiting for a monitor lock and is trying to access a section of code that is locked by some other thread.

この状態を再現してみましょう。

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 enters this
        }
    }
}

このコードでは:

  1. t1t2の2つの異なるスレッドを作成しました

  2. t1が開始し、同期されたcommonResource()メソッドに入ります。これは、1つのスレッドのみがアクセスできることを意味します。このメソッドにアクセスしようとする他のすべての後続のスレッドは、現在のスレッドが処理を終了するまで、それ以降の実行からブロックされます。

  3. t1がこのメソッドに入ると、whileループは無限に保たれます。これは、他のすべてのスレッドがこのメソッドに入ることができないように、重い処理を模倣するためだけのものです。

  4. ここで、t2を開始すると、t1,によって既にアクセスされているcommonResource()メソッドに入ろうとするため、t2BLOCKED状態に保たれます。

この状態で、t2.getState()を呼び出し、次のように出力を取得します。

BLOCKED

3.4. 待っています

A thread is in WAITING state when it’s waiting for some other thread to perform a particular action.According to JavaDocsの場合、次の3つのメソッドのいずれかを呼び出すことにより、どのスレッドもこの状態に入ることができます。

  1. object.wait()

  2. thread.join()または

  3. LockSupport.park()

wait()およびjoin()では、そのシナリオについて次のセクションで説明するため、タイムアウト期間を定義しないことに注意してください。

wait()notify()、およびnotifyAll()の使用について詳しく説明しているa separate tutorialがあります。

とりあえず、この状態を再現してみましょう。

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());
    }
}

ここで行っていることについて話し合いましょう。

  1. t1を作成して開始しました

  2. t1t2を作成し、それを開始する。

  3. t2の処理が継続している間、t2.join()を呼び出します。これにより、t2の実行が完了するまで、t1WAITING状態になります。

  4. t1t2が完了するのを待っているため、t2からt1.getState()を呼び出しています

ここでの出力は、ご想像のとおりです。

WAITING

3.5. 時限待ち

スレッドは、別のスレッドが指定された時間内に特定のアクションを実行するのを待っているとき、TIMED_WAITING状態にあります。

According to JavaDocs、スレッドをTIMED_WAITING状態にする5つの方法があります。

  1. thread.sleep(long millis)

  2. wait(int timeout)またはwait(int timeout, int nanos)

  3. thread.join(longミリ秒)

  4. LockSupport.parkNanos

  5. LockSupport.parkUntil

Javaでのwait()sleep()の違いの詳細については、this dedicated article hereを参照してください。

とりあえず、この状態をすばやく再現してみましょう。

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. 終了しました

これは、デッドスレッドの状態です。 It’s in the TERMINATED state when it has either finished execution or was terminated abnormally.

スレッドを停止するさまざまな方法について説明するa dedicated articleがあります。

次の例でこの状態を実現してみましょう。

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

4. 結論

このチュートリアルでは、Javaのスレッドのライフサイクルについて学びました。 Thread.State列挙型で定義された6つの状態すべてを調べ、簡単な例でそれらを再現しました。

コードスニペットはほとんどすべてのマシンで同じ出力を提供しますが、例外的なケースでは、スレッドスケジューラの正確な動作を判断できないため、異なる出力が得られる場合があります。

そして、いつものように、ここで使用されるコードスニペットはavailable on GitHubです。