JAVA programing




スレッドの停止

さて、スレッドの生成方法についてはのべきました。では、生成されたスレッドはいつ停止するのでしょうか。
それは、呼び出されたrun()メソッドの実行が終了した時点です。これはThreadを継承した場合でもRunnableインタフェースを実装した場合でも同じです。run()メソッドの最後に到達した場合や、run()メソッド中でreturn文を実行するなど、run()メソッドから復帰したらスレッドは停止し、スレッドの実行のために割り当てられたりソースは開放されます。

ただし、スレッドに対応するThreadオブジェクトはスレッドが終了しても消えてなくなるわけではありません。これはスレッドが終了した後もThreadオブジェクトを使う場合があるからです。

例をあげます。スレッドの状態はThreadオブジェクトのisAlive()メッソドを通して調べることができます。

boolean isAlive()

このisAlive()メソッドで、スレッドの実行中かどうか調べることができます。スレッドが実行中の場合はtrue、そうでない場合はfalseが返されます。
以下にこれを使ったプログラムを示します。
public class AliveTest extends Thread { static public void main(String args[]){ AliveTest alive = new AliveTest(); System.out.println("Thread is alive ? " + alive.isAlive() ); alive.start(); System.out.println("Thread is alive ? " + alive.isAlive() ); while( alive.isAlive() ); System.out.println("Thread is alive ? " + alive.isAlive() ); } public void run(){ for(int i=0; i<10000; i++); System.out.println("Thread is terminated"); } }


このプログラムを実行すると以下のようになります。

D:\home\arakaki\java\java>java AliveTest Thread is alive ? false Thread is alive ? true Thread is terminated Thread is alive ? false D:\home\arakaki\java\java>


start()メッソドを呼び出す前にisAlive()メソッドを呼び出すと、まだスレッドは実行されていないので、isAlive()メッソドはfalseを返します。
start()メッソドを呼び出した後にisAlive()メソッドを呼び出すと、スレッドが実行されたので、isAlive()メソッドはtrueを返します。

続いて以下のメソッドで、isAlive()メッソドがfalseになるのを待ちます。

while( alive.isAlive() );

run()メッソドの最後の「Thread is terminated」の出力後、このisAlive()メッソドがfalseになることが結果からわかります。
つまり、run()メッソドが終了し、スレッドが停止すると、isAlive()メッソドはfalseを返します。

このように、スレッドが停止した後でも、Threadオブジェクト自体は勝手に消去されたりしません。

本当にわかっているかな? isAlive()メッソドは何のメッソドか→ aliveオブジェクトのメッソド
オブジェクトが終了していたらメッソドは呼び出せない。


なお、Threadオブジェクト自体を消したい場合には、他のオブジェクト同様、オブジェクトの参照をなくすことでガベージコレクションの対象になり、自動的に消去されます。たとえば先ほどの例では、

alive = NULL;

とするだけです。

また、スレッドが停止した後、もう一度Threadオブジェクトのstart()メソッドを呼び出してもスレッドは生成されません。Threadオブジェクトの再利用はできませんので、注意して下さい。


スレッドの停止を待つ

さて、今の例でスレッドが停止するのを待つのに、

while(alive.isAlive());

というコードを使いました。このコードでは、スレッドが全速でisAlive()メソッドを使ってスレッドの状態を調べているので、CPU時間を無駄に浪費します。
スレッドが停止するのを待つためにjoin()メソッドがThreadクラスに用意されていますので、join()メソッドを使うように変更しましょう。

void join()

join()メソッドは指定したスレッドが停止するまで待ちます。次のように待ち時間を指定する形式もあります。なお、このjoin()メソッドもsleep()メソッドと同様、InterruptedExeptionをthrowする場合があるので対処が必要です。

void join(long millis)

void join(long millis, int nanos)

join()メソッドを使って先のプログラムを書き直したものを以下に示します。

public class AliveTest2 extends Thread { static public void main(String args[]){ AliveTest2 alive = new AliveTest2(); System.out.println("Thread is alive ? " + alive.isAlive() ); alive.start(); System.out.println("Thread is alive ? " + alive.isAlive() ); try{ alive.join(); } catch(InterruptedException e ){ } System.out.println("Thread is alive ? " + alive.isAlive() ); } public void run(){ for(int i=0; i<10000; i++); System.out.println("Thread is terminated"); } }



スレッドを停止するには

別のスレッドからとめる

次に、あるスレッドから他のスレッドを停止する方法について説明します。すでに説明したようにrun()メソッドが終了すればスレッドは停止します。つまり、他のスレッドの終了指示でrun()メッソドを終了するようにプログラムを記述すればよいのです。終了指示を変数を使って行うプログラムの流れとプログラムを以下に示します。

このプログラムを実行すると、スレッドが新たに作成され、run()メッソドが実行されます。
main()メソッドはスレッドを生成した後、sleep()により2秒間停止します。
一方、run()メソッドの方ではwhile文を実行します。このwhile文では、stopFlagがfalseの間、「Now Thread is running.」というメッセージを表示し続けます。stopFlagの初期値はfalseですので、画面上に「Now Thread is running.」というメッセージがあふれます。
2秒間の停止後、main()メソッドではstopFlagをtrueに変更します。
while文はstopFlagがtrueになったので、run()メッソドのスレッドはwhile文を抜けて「Thread is terminated.」というメッセージを表示してrun()メソッドから復帰し、スレッドが終了します。

main()
新たなスレッドを生成
alive.join()
aliveスレッドの停止待ち
―――――――――→ run()
aliveスレッドの実行開始
"Thread is Terminated"の出力
aliveスレッドの終了
←―――――― ―――――――――― ―――――――
alive.join()終了
"Thread is Alive? False"出力


課題:2行目を完成させよ。
public class StopTest extends Thread { v b s = static public void main(String args[]){ StopTest s = new StopTest(); s.start(); try{ Thread.sleep(2000); } catch( InterruputedException e ){ } System.out.println("now stopFlag is true "); s.stopFlag = true; } public void run(){ while( !stopFlag ){ System.out.println("Now Thread is running."); } System.out.println("Thread is terminated."); } }


実際にこのプログラムを実行すると次のような結果が得られます。
D:\home\arakaki\java\java>java StopTest … Now Thread is running. Now Thread is running. Now Thread is running. Now Thread is running. Now Thread is running. now stopFlag is true Thread is terminated. D:\home\arakaki\java\java>

stop()メソッド中で止める

もう1つの例を示します。前回の最後から3番目のプログラムで取り上げたインナークラスを使ったアプレットでは、スレッドを停止する処理が入っていなかったので、アプレットが停止した後でもスレッドは動作し続けます。そこで、アプレットが停止するときに呼び出されるstop()メソッド中でメソッドを停止するようにしましょう。

以下にプログラムの流れとプログラムを示します。先ほどの例と同様、停止用のフラグ変数を追加し、stop()メッソドではstopFlagをtrueにし、runHelloWorld()メソッド中では単純な無限ループではなくstopFlagに依存したループに変更しているだけです。

main()
新たなスレッドを生成
├――― ―――――――――→ while : stopFlagがfalseの間
"Now Thread is running"出力
2秒間停止 |
stopFlag = true ―――――――――― ―――→while文終了
"Thread is terminated"出力
スレッドの終了




/* <applet code=HelloApplet6.class width="200" height="100"></applet> */ import java.awt.Graphics; import java.applet.Applet; public class HelloApplet6 extends Applet{ final String HelloWorld = "Hello World"; // 何文字目までを表示するかを格納する変数 volatile int idx; // スレッド停止用フラグ volatile boolean stopFlag; Thread helloThread = null; public void init(){ idx = 0; stopFlag = false; } public void paint(Graphics g){ // 行頭からidx文字目までを表示 g.drawString(HelloWorld.substring(0,idx),30,30); } public void start(){ // idx更新用スレッドを作成 if(helloThread == null){ helloThread = new Thread(){ public void run(){ // インナークラスなので、HelloApplet6クラスのメソッドが呼び出せる runHelloWorld(); } }; helloThread.start(); } } public void stop(){ // アプレット停止時にスレッドも停止するようにする stopFlag = true; helloThread = null; } private void runHelloWorld(){ while(!stopFlag){ try{ // 400ms停止 Thread.sleep(400); } catch(InterruptedException e){ } // idxを1増加する。Hello Worldの文字数を越えた場合は0に戻す。 idx = (idx < HelloWorld.length() ? idx+1 : 0); repaint(); } } }



プログラムの終了条件

さて、最後に、プログラムの終了とスレッドの関係について。
スレッドが終了するのは、run()メソッドが終了したときであった。では、プログラム自体はいつ終了するのでしょうか。
少し考えてみるとmain()が終了したときにプログラムが終了するような気がしますが、どうでしょうか。

以下のプログラムを見てください。このプログラムでは、mainThread変数にmain()メソッドを実行しているスレッドを格納し、新たなスレッドを生成しています。
生成されたスレッドでは、mainThread.join()をつかってmainスレッドが停止するのを待った後(1秒後に)「run() is running」というメッセージを出力します。もしもmainスレッドが終了したときにプログラムを終了するのであれば、この「run() is running」というメッセージは出力されないはずです。

public class ExitTest extends Thread{ Thread mainThread = null; static public void main(String args[]){ ExitTest e = new ExitTest(); e.mainThread = Thread.currentThread(); e.start(); System.out.println("main is terminated."); } public void run(){ if(mainThread != null){ try{ mainThread.join(); Thread.sleep(1000); } catch( InterruptedException e ){ } System.out.println("run() is running. "); } } }


では実行してみましょう。
D:\home\arakaki\java\java>java ExitTest main is terminated. run() is running. D:\home\arakaki\java\java>

予想に反して「run() is running」というメッセージも出力されます。
実は、プログラムが終了するのはmain()メソッドが終了したときではなく、スレッドがすべて終了したときになります。正確に言うと、プログラムが終了するのはユーザースレッドがすべて終了したときになります。
通常、スレッドをユーザープログラムから生成した場合はユーザスレッドになります。
一方で、ユーザースレッドとは異なり、プログラムが終了する際に停止を待たないスレッドのことを、デーモンスレッドと呼びます。スレッド生成時にデーモンスレッドになるのは、生成したスレッドがデーモンスレッドの場合だけです。ユーザースレッドとデーモンスレッドの切り替えはThreadクラスのsetDaemon()メソッドで行います。

setDaemon(boolean on)
onがtrueの場合には指定したスレッドはデーモンスレッドになります。

なお、このsetDaemon()メソッドはスレッドをstart()する前に呼び出してください。start()した後に呼び出した場合は、IlleagalThreadStateExceptionがthrowされます。

では、実際にsetDaemon()の動きの違いを見てみましょう。次のプログラムは、新たに生成するスレッドをデーモンスレッドにしています。
public class ExitTest2 extends Thread{ Thread mainThread = null; static public void main(String args[]){ ExitTest2 e = new ExitTest2(); e.mainThread = Thread.currentThread(); e.setDaemon(true); e.start(); System.out.println("main is terminated."); } public void run(){ if(mainThread != null){ try{ mainThread.join(); Thread.sleep(1000); } catch( InterruptedException e ){ } System.out.println("run() is running. "); } } }


実行してみましょう。
D:\home\arakaki\java\java>java ExitTest2 main is terminated. D:\home\arakaki\java\java>
プログラムがデーモンスレッドの終了を待たないことがわかります。なお、デーモンスレッドの例としては、JvaVMのガベージコレクタなどがあります。


まとめ