Thread の基本
こちらでは、Thread 生成の基本について紹介します。 排他制御関係や、thread 間の同期については、別のページを準備予定です。
Threadとは
スレッドというのは、それぞれ別々に動くことのできる処理の流れのことです。 普通の関数呼び出しとかは、順番に(逐次的に)処理していきますよね。 一方で、スレッドを作ると、二つの処理を同時並行的に処理できるわけです。
生成されたスレッドは、同じメモリ空間を共有することになります。 互いに作ったオブジェクトとか、参照さえわかれば見放題です。こんな感じ。
一方で、一般のプロセスは、互いのメモリ空間は別々です。以下の絵のような感じ。同じプログラムを二つ同時に走らせたとしましょう。別々のプロセス上での実行になりますが、ある変数のアドレスが共通でも、当然「別のもの」を触っているはずですよね?
複数のスレッドが、一つのオブジェクトに同時に触れますので、排他制御なども必要になります。 オブジェクトの中には、排他制御が適切におこなわれているオブジェクトもあり、MultiThread Safe あるいは Thread Safe などと呼ばれます。
Thread 生成
自分で Thread 生成する例は以下のとおり。annonymous inner class を使ってますが、Runnable を実装したクラスを別途作ってもらっても構いません。
Thread thread = new Thread(new Runnable() {
public void run() {
/* thread でやりたい処理 */
}
});
thread.start();
lambda expression を使うとこんな感じ。
Thread thread = new Thread(()->{
/* thread でやりたい処理 */
});
thread.start();
こちらが実例(RunnableSample)。
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
Thread threadB = new Thread(new Runnable() {
public void run() { /* thread の中身 */
try {
while(true) {
System.out.println("Input Please");
Thread.sleep(3*1000); //3sec
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// threadB.setDaemon(true);
threadB.start();
String line = in.nextLine();
System.out.println("Your input is ...." + line);
}
今回の例では、以下のスレッドが存在します。
- main thread: threadB を生成し、標準入力から一行読み込んで終了。
- threadB: 3秒毎に、“Input Please"と出力し続ける thread。
run()
の中身がその処理内容
さて、実行すると、こんな結果になります。
Input Please // threadB: 生成直後
Input Please // threadB: 3秒後
hoge // main への入力
Your input is ....hoge // main thread 動く、で、直後終了
Input Please // threadB: 6秒後
Input Please // threadB: 9秒後
.... // ストップさせるまで、延々と続く
なんとなく、「入力終わったら、終了しろよ」とか思うかもしれませんが。。。 スレッドとプログラム終了の関係については、このページの最後に説明します。
sleep()
前述のプログラムでは、ついでに、sleep(long)
をつかって、Thread を眠らせてみました。指定した時間 Thread が眠ります。 Thread#interrupt()
で叩き起こすことも出きるので,java.lang.InterruptedException 対応が必要です。
Timer
以下では、例として、Timer クラスの利用例を紹介しておきます(TimerTaskSample)。
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
Timer timer = new Timer(true);
TimerTask task = new TimerTask() {
public void run() { /* task の中身 */
System.out.println("Input Please");
}
};
timer.schedule(task, 1*1000, 5*1000); /* taskを開始1秒で実行し、5秒ごとに再実行 */
String line = in.nextLine();
System.out.println("Your input is ...." + line);
task.cancel(); /* task cancel*/
System.out.println("Please re-input the text...." + line);
String line2 = in.nextLine();
System.out.println("Your inputs are ...." +
(line.equals(line2)? "same.": "different."));
}
登場人物は、
- java.util.TimerTask
task
: 今回も annonymous inner class として作成。処理して欲しい内容を run() に記述TimerTask
は java.lang.Runnable をimplements
- java.util.Timer
timer
:TimerTask
を定期的に実行- ここでは、最初 1 秒後に実行し、5秒毎に繰り返す(
cancel()
されるまで)
- ここでは、最初 1 秒後に実行し、5秒毎に繰り返す(
というわけで、このプログラムは、
- 最初のユーザ入力があるまでは、“Input Sample"と繰り返し、
- 入力が終わると、次の入力を促され、(ただし、今度はうるさくいわれない)
- 次の入力が終わると終了
というプログラムです。
実行例:
Input Please // 1秒後
Input Please // 6秒後
Input Please // 11秒後
hoge // ユーザ入力
Your input is ....hoge
Please re-input the text....hoge
hogera // ユーザ入力(この間、五月蝿くいわれない)
Your inputs are ....different
(終了, Timer が deamon 指定されているので)
プログラム終了
普通、プログラムは、active なスレッドがある間は、終了しません。注意点としては、
Thread#setDeamon(true)
で、デーモンスレッドと指定すると、プログラム終了判定に影響しない。- GUI 用 thread は、visible なものがあれば、active とみなす
というわけで、 前述の RunnableSample の例では、コメントアウトされていた setDeamon(true)
を戻してやると、入力のタイミングで終了するようになります。