新しいスレッドの具体的な処理の内容を記述する方法には 2種類の方法がありました。ひとつは Threadクラスを拡張した独自のクラスを作成する方法、もうひとつは Runnableインターフェスを実装した独自のクラスを作成し、それを Threadクラスのコンストラクタの引数に渡す方法です。
どちらの方法をとったとしても、スレッドを開始するためには、Thread型(とみなせる)インスタンスを作成し、その startメソッドを呼び出す必要があります。具体的には、以下のようなソースコードを記述しました。
thread.start();
startメソッドを呼び出すことで、新しいスレッドが生成されるということでした。
ところで、あるスレッド(例えば mainメソッドを実行しているスレッド)が、別のスレッドを開始させることができるのならば、あるスレッドが既に実行している別のスレッドを停止させることもまたできる…と思わないでしょうか。そう、できるのです。それならばそのメソッド名は…予想されるとおり、以下のように記述することができます(ただし、後述するように、この方法は非推奨です)。
thread.stop();
さっそく実装する…前に。
起動時に、ビープ音(ずっと昔のコンピュータでのピーという音)を鳴らすプログラムを作成します。というのもこの節でも 2つのスレッドを扱いますが、複数のスレッドが画面に対して出力すると表示が乱れます。1つのスレッドが画面を利用し、もうひとつのスレッドが音を出すことにすれば分かりやすいため、まずはビープ音を鳴らすプログラムを作成してみます。
ビープ音を鳴らすためのメソッドは beepで、このインスタンスメソッドは、Toolkit抽象クラスにて定義されています。以下のコマンドを実行すると ビープ音が 1回だけ鳴ります。
toolkit.beep();
また beepメソッドはインスタンスメソッドなので、インスタンスが必要です。
詳しく説明するならば次の通りですが、ここでは意味よりも使い方さえ分かれば大丈夫です。Toolkitクラスの staticなメソッドである getDefaultToolkitを呼び出すと Tookitインスタンスを取得できるようになっているので取得します。なお、Toolkitクラスは抽象クラスです。なので、正確に言えば、 getDefaultToolkitメソッドを呼び出すと、Toolkitクラスを継承した(名称不明の)クラスをインスタンス化したオブジェクトを取得できることになります。
なお、Toolkitクラスの staticなメソッドである getDefaultToolkitの詳細と、Toolkitクラスのインスタンスメソッドである beepの詳細について気になる方は、説明書(API)を参照してください。Java SE 7(Java 1.7)の説明書(API)を掲示しておきます→getDefaultToolkitメソッド・beepメソッド。
Q501/Q501.java
import java.awt.Toolkit;
/**
* ビープ音を鳴らします。
*/
public class Q501 {
/**
* メインメソッド。
* @param args 引数
*/
public static void main(String[] args) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
for(int i = 0; i < 5; i++) {
toolkit.beep();
try {
Thread.sleep(80);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
プログラムを実行するとビープ音が鳴ったでしょうか。今の Javaではサウンドとしてビープ音が流れるようですね。
起動時に、ビープ音を定期的に鳴らすスレッドを作成し、それをメインスレッドから止めるようなプログラムを作成します。ユーザからの入力を待ちながら、ビープ音を鳴らすことができる…これもマルチスレッドだからこそ簡単に実現できることです。
Q502/MySystem.java(ライブラリをそのまま利用します)
Q502/BeepThread.java
import java.awt.Toolkit;
/**
* 定期的に音を鳴らすスレッドです。
*/
public class BeepThread extends Thread {
@Override
public void run() {
Toolkit toolkit = Toolkit.getDefaultToolkit();
while(true) { // 無限ループ
toolkit.beep();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Q502/Q502.java(19行目に黄線と取り消し線が生じますが問題ありません)
/**
* スレッドを止めます。この方法は非推奨です。
*/
public class Q502 {
/**
* メインメソッド。
* @param args 引数
*/
public static void main(String[] args) {
BeepThread beepThread = new BeepThread();
beepThread.start();
int x;
do {
x = MySystem.in.getInt("1を入力してスレッドを止めます");
} while(x != 1);
beepThread.stop(); // 非推奨なメソッド呼び出し
do {
x = MySystem.in.getInt("1を入力してプログラムを終了します。");
} while(x != 1);
}
}
実行結果の例は以下の通り。
1を入力してスレッドを止めます? 1 1を入力してプログラムを終了します? 1
「1を入力してスレッドを止めます?」の表示がある間は、ビープ音が 1秒間隔で流れているでしょうか。そして、1を入力するとビープ音は止まります。
BeepThreadメソッドを見てみましょう。whileループの条件は trueと記述されています。そしてそのループ内にはループを脱出するための処理が書かれていません。永遠に続くようにも見えるこのスレッドの実行は、しかし、メインメソッド側から Threadクラスの stopメソッドが呼び出さると終了することになります。
先のソースコードを見ます。既に分かっていることと思いますが、stopメソッドの部分には以下のように明らかに取り消し線のようなものが記述されています。
Threadクラスのインスタンスメソッドである stopについて説明書(API)を参照すると以下のような表現が見当たります。なお、詳細について気になる方は、説明書(API)を参照してください。Java SE 7(Java 1.7)の説明書(API)を掲示しておきます→stopメソッド。
@Deprecated public final void stop() 非推奨。 このメソッドは本質的に安全ではありません。(以下略)
「@deprecated」というアノテーション、それから「非推奨」という言葉があります。そう、このメソッドは、非推奨…おすすめしないということです。
プログラミング言語は日々進化していきます。Javaもバージョンを重ねるにつれて新しいクラスが追加され、新しいメソッドが追加されていきました。一方で、当初は良かれと思って実装されたものが、後の改修によって廃止となることがあります。「廃止となる」と言っても、そのまま削除してしまったら、既存のプログラムが動作しなくってしまいます。ということで、当分の間、非推奨として残すことにするという形になっています。
Threadクラスの stopメソッドは、1995年、初代の JavaであるJDK 1.0に実装されました。しかし、1997年の JDK1.1で既に非推奨となっています。言い換えれば、1995年~1997年に作成されたプログラムが今でもコンパイルエラーとならないようにするために、このメソッドが残っているということでもあります。幸いにして先のプログラムは正常に動作した…と思いますが、1997年には非推奨とされたメソッドを、今、新しいプログラムで使用するのは得策ではありません。さて、次では stopメソッドを使用しないでスレッドを止める方法を見ていきます。
Q503/MySystem.java(ライブラリをそのまま利用します)
Q503/BeepThread.java
import java.awt.Toolkit;
/**
* 定期的に音を鳴らすスレッドです。
*/
public class BeepThread extends Thread {
/**
* スレッドがアクティブの場合、true。
*/
private boolean isActive = true;
/**
* スレッドを停止します。
*/
public void stopThread() {
this.isActive = false;
}
@Override
public void run() {
Toolkit toolkit = Toolkit.getDefaultToolkit();
while(this.isActive) {
toolkit.beep();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 終了直前の動作
for(int i = 0; i < 5; i++) {
toolkit.beep();
try {
Thread.sleep(80);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Q503/Q503.java
/**
* スレッドを止めます。
*/
public class Q503 {
/**
* メインメソッド。
* @param args 引数
*/
public static void main(String[] args) {
MySystem.in.setPrompt("--->");
BeepThread beepThread = new BeepThread();
beepThread.start();
int x;
do {
x = MySystem.in.getInt("1を入力してスレッドを止めます");
} while(x != 1);
beepThread.stopThread();
do {
x = MySystem.in.getInt("1を入力してプログラムを終了します");
} while(x != 1);
}
}
実行結果の例は以下の通り。
1を入力してスレッドを止めます---> 1 1を入力してプログラムを終了します---> 1
今回のプログラムでは、Beepのスレッドが終了する直前に、ビープ音を 5回連続して鳴らすというおまけをつけています。スレッドが終了する処理の流れの参考になればと思います。
今回はメインメソッド側から BeepThread側のフラグを変更するためのメソッドを呼び出しています。その結果、isActiveフラグが falseになり、結果として、その後、Beepのスレッドは終了します。Q502のプログラムは「メインメソッドが BeepThreadを終了させる」のに対して、Q503のプログラムは「メインメソッドはフラグを変更するだけ。そして BeepThreadは自発的に終了する」という点が大きな違いです。
あ… setPromptメソッドはおまけです。MySystemに用意していた隠し機能でした。