Thread の基本

こちらでは、Thread 生成の基本について紹介します。 排他制御関係や、thread 間の同期については、別のページを準備予定です。

Threadとは

スレッドというのは、それぞれ別々に動くことのできる処理の流れのことです。 普通の関数呼び出しとかは、順番に(逐次的に)処理していきますよね。 一方で、スレッドを作ると、二つの処理を同時並行的に処理できるわけです。

生成されたスレッドは、同じメモリ空間を共有することになります。 互いに作ったオブジェクトとか、参照さえわかれば見放題です。こんな感じ。

threads.png

一方で、一般のプロセスは、互いのメモリ空間は別々です。以下の絵のような感じ。同じプログラムを二つ同時に走らせたとしましょう。別々のプロセス上での実行になりますが、ある変数のアドレスが共通でも、当然「別のもの」を触っているはずですよね?

processes.png

複数のスレッドが、一つのオブジェクトに同時に触れますので、排他制御なども必要になります。 オブジェクトの中には、排他制御が適切におこなわれているオブジェクトもあり、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() に記述
  • java.util.Timer timer: TimerTask を定期的に実行
    • ここでは、最初 1 秒後に実行し、5秒毎に繰り返す(cancel() されるまで)

というわけで、このプログラムは、

  • 最初のユーザ入力があるまでは、“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) を戻してやると、入力のタイミングで終了するようになります。