4. スレッドの制御

4.1. 優先度の設定

すべてのスレッドは優先度を持っています。資源が競合した際、一般的に高い優先度を持つスレッドの方が優先的に実行されます。

スレッドの優先度はgetPriorityメソッドで取得する事ができます。

1
2
3
Thread thread = new Thread(runnable);
System.out.println(thread.getPriority());
thread.start();

優先度を設定するにはsetPriorityメソッドを用います。引数に優先度を表す数字を指定しますが、この値は「Thread.MAX_PRIORITY」「Thread.MIN_PRIORITY」クラス変数で定義されている最小値(1)から最大値(10)の範囲に収まるものでなければなりません。なおデフォルトで与えられる優先度は「5」であり、この値は「Thread.NORM_PRIORITY」クラス変数に定義されています。

1
2
thread.setPriority(Thread.MAX_PRIORITY);
thread.setPriority(2);

優先度の違いによる資源の割り当て方は、OSやJava仮想マシンの実装に大きく依存します。高い優先度を設定すると、ある程度は優先的に実行されるかもしれませんが、もしかしたら実際には全く優先されないかもしれません。万が一にも優先度の低いスレッドが優先度の高いスレッドより先に実行されたら困るような場面では、この優先度の仕組みを使うべきではありません。

(実習課題1)

2章の実習課題2のプログラムで以下の点を試しなさい。可能であればLinuxとWindowsの両方で動作させ、優先度を設定したときの動作に違いがあるかどうかを確認すること。

・それぞれのスレッドに異なる優先度を設定する事。 ・設定した優先度の違いに応じて、結果がどのようになるか確かめる事。

4.2. スレッドの休止・中断

ここでは、スレッドの処理を一時休止したり、他のスレッドに割り込みをかけるためのメソッドを説明します。

sleep()

sleepメソッドはThreadクラスのクラス(static)メソッドです。sleepメソッドは指定した時間だけ現在実行中のスレッドを休止させます。sleepメソッドはクラスメソッドのため、sleepメソッドを実行するスレッド以外の別のスレッドを休止することはできません。

sleepメソッドでの時間の指定方法は2種類あります。

sleep(long millis);

sleep(long millis, int nanos);

これらのメソッドは、millisミリ秒(1000分の1秒)、またはmillisミリ秒+nanosナノ秒(100万分の1秒)だけスレッドを休止させます。スレッドを休止している間、他に実行可能なスレッドがあれば、そちらに処理が移るかもしれません。ただし、sleepメソッドを呼び出したスレッドは、取得しているロックを解放しません。ロックを確保したままsleepを実行すると、そのロックを取得したいスレッドを必要以上に待たせてしまうおそれがあります。ロックを解放した状態でスレッドを休止させたいときは、次章で説明するwaitメソッドを利用することも検討しましょう。

スレッドは、sleepメソッドで指定した時間経過しても直ちに動作を再開するわけではありません。指定した時間経過後、動作可能な状態にはなりますが、他のスレッドがなにか処理を実行中の場合、そのスレッドの実行が継続されます。sleepメソッドを実行したスレッドは、他の多くのスレッドと同じように、Java仮想マシンによって時間が割り当てられるのを待機します。

sleepメソッドは、休止中に他のメソッドから割り込みがかけられたときにInterruptedExceptionが発生しますので、例外処理を記述しなければいけません。割り込みは、あとで説明するinterruptメソッドによって発生させることができます。

yield()

yieldメソッドは、現在処理中のスレッドを一時休止し、他のスレッドに実行の機会を与えます。Java仮想マシンが複数のスレッドにどのように時間を割り当てるかというのは、実装によってまちまちです。実装によっては、他にも実行可能なスレッドがあるにもかかわらず、あるスレッドからなかなか処理が移らないということがあります。そのようなことを防ぐためにyieldメソッドを用います。

yieldメソッドもsleepメソッドと同じくクラス(static)メソッドです。休止させるスレッドを指定することはできません。休止できるのは、yieldメソッドを実行しようとしているスレッド自分自身だけです。

yieldメソッドはsleepメソッドに似ていますが、sleepメソッドのように休止する時間は指定できません。yieldメソッドを実行すると、他のスレッドに処理が移るかもしれませんが、そのときでもyieldメソッドは引き続き実行可能な状態となります。sleep(0)を呼び出したのと同じような効果があると考えたらよいでしょう。

interrupt()

interruptメソッドは休止中のスレッドに割り込みを入れるメソッドです。割り込みを入れることのできるスレッドは、joinメソッドやsleepメソッドの実行により待機中のメソッド、または次節で説明するObjectクラスのwaitメソッドで待機中のメソッドです。

割り込みを入れられたメソッドは、java.lang.InterruptedException例外を発生し、処理を再開します。

4.3. 推奨されないメソッド

Threadクラスには、現在では使用が推奨されていないメソッドがいくつかあります。原則としてこれらのメソッドを使用してはいけません。

stop()

スレッドを強制的に停止させるメソッドです。スレッドがどのような状態にあってもスレッドを停止させるため、そのスレッドが操作中のオブジェクトのデータに不整合が生じるおそれがあります。

suspend()

スレッドを直ちに中断させるメソッドです。中断されたメソッドは次のresume()メソッドによって再開されます。suspendメソッドは、スレッドが取得しているロックを解放しないため、デッドロックの原因となりやすいという問題があります。

なお、デッドロックとは、複数のスレッドがオブジェクトのロックを確保できない状態で処理が進まなくなってしまう状態のことをいいます。たとえば、スレッド1がオブジェクトAのロックを確保し、スレッド2がオブジェクトBのロックを確保している状態を考えます。このとき、スレッド1がさらにBのロックを要求し、スレッド2がAのロックを要求すると、AとBのロックは絶対に解放されない状態となります。このような状態をデッドロックといいます。

resume()

suspendメソッドによって中断されているスレッドの動作を再開するメソッドです。suspendメソッドを使用しなければ、resumeメソッドを使用することもありません。

4.4. スレッドを停止させるには

stopメソッドは推奨されないメソッドでした。それでは、スレッドを停止させるにはどうすれよいのでしょうか。

スレッドが自分自身を終了させるには、実行中のrunメソッドを終了するようにしておきます。startメソッドによって新しく起動されたスレッドは、runメソッドが終了すると消滅します。

自分以外のスレッドを終了させるには、一般的に次のような方法を用います。

  • runメソッドを実装したクラスで、スレッドの実行状態を表現するboolean型変数を宣言する。
  • runメソッド内で、繰り返しスレッドの実行状態を確認し、実行状態が「終了」を示す値になっていればrunメソッドが終了されるようにしておく。
  • そのスレッド実行状態を変更するためのメソッドを宣言する。
  • スレッド実行状態を「実行中」を示す値に設定し、新しいスレッドを起動する。
  • 外部から、スレッド実行状態を変更するためのメソッドを呼び出す。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class someThread extends Thread{
    private boolean running = true;
    public void start(){
        new Thread(this).start();
    }
    public void run(){
        while(running){
        ...... //スレッドで実行したい処理
        }
    }
    public void stopRunning(){
        running = false;
    }
}

スレッドの実行状態は、runningというメンバ変数で表現しています。runningの初期値はtrue(実行中を表す)とします。スレッドが起動したあと、runメソッド内のwhileループは、runnningがtrueである限り繰り返し実行されます。

外部からスレッドを停止させるにはstopRunningメソッドを呼び出します。ここでrunningの値がfalseに変更されるので、runメソッド内のwhileループは、次回の条件チェックの際に繰り返し処理を終了します。ループを抜けることによってrunメソッドも終了し、スレッドは消滅します。

(実習課題2)


2章の実習課題2のプログラムに以下の機能を追加しなさい。

  • 一定時間内に終了しないソーティングスレッドを強制終了させる機能。
  • 処理時間の管理はソーティングスレッド以外で行う事。
  • また強制終了までの時間はプログラムの実行時の引数で指定できるようにする事。