6. スレッドクラスの変更点

JDK5.0ではスレッドにいくつかの変更点が加えられました。 java.lang.Threadクラスの拡張に加え、新たにjava.util.concurrentパッケージが追加されました。 concurrentパッケージは、マルチスレッド時の同期処理などに有用なクラスを提供します。

6.1. Threadクラスの変更点

JDK5.0により、以下が可能になりました。

  • スタックトレースの取得
  • キャッチされない例外の取得
  • スレッドの状態の取得
  • 1ミリ秒未満のスリープ

6.2. スタックトレースの取得

今までは例外をキャッチすることでしかスタックトレースを取得することが出来ませんでしたが、 JDK5.0で追加されたgetStackTrace()、getAllStackTrace()メソッドにより、 スレッドのスタックトレースを取得することが出来るようになりました。

getStackTrace()のサンプル

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class ThreadSample implements Runnable {

    public static void main(String[] args) {

        StackTraceElement[] elements = Thread.currentThread().getStackTrace();
        for(StackTraceElement element : elements){
            System.out.println(element);
        }
    }
}

getStackTrace()メソッドの返り値は、「java.lang.StackTraceElement」オブジェクトの配列です。 次のように出力されます。

1
2
3
java.lang.Thread.dumpThreads(Native Method)
java.lang.Thread.getStackTrace(Unknown Source)
ThreadSample.main(ThreadSample.java:14)

getAllStackTrace()メソッドは、現在稼動している全てのスレッドのスタックトレースを返します。 返り値はMap型で、キーはスレッド、値はそのスレッドに関するStackTraceElemntの配列が入っています。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public static void main(String[] args) {
        Map<Thread, StackTraceElement[]> maps = Thread.getAllStackTraces();
        Iterator itr = maps.entrySet().iterator();
        while(itr.hasNext()){
            Map.Entry entry = (Entry) itr.next();
            Thread thread = (Thread) entry.getKey();
            StackTraceElement[] trace = (StackTraceElement[]) entry.getValue();

            System.out.println(thread);
            for(int i=0; i<trace.length; i++){
                System.out.println(trace[i]);
            }
            System.out.println();
        }
    }

次のように出力されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
```
Thread[Finalizer,8,system]
java.lang.Object.wait(Native Method)
java.lang.ref.ReferenceQueue.remove(Unknown Source)
java.lang.ref.ReferenceQueue.remove(Unknown Source)
java.lang.ref.Finalizer$FinalizerThread.run(Unknown Source)
Thread[Reference Handler,10,system]
java.lang.Object.wait(Native Method)
java.lang.Object.wait(Unknown Source)
java.lang.ref.Reference$ReferenceHandler.run(Unknown Source)
Thread[main,5,main]
java.lang.Thread.dumpThreads(Native Method)
java.lang.Thread.getAllStackTraces(Unknown Source)
ThreadSample.main(ThreadSample.java:15)
Thread[Signal Dispatcher,10,system]
```

スタックトレースが追加されたことにより、スレッドの状態を取得しやすくなりました。

6.3. キャッチされない例外の取得

例として以下のソースを見てください。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
public class ThreadSample implements Runnable {
    public static void main(String[] args) {
       Thread thread = new Thread(new ThreadSample());
       thread.start();

       while(true){
           try {
            Thread.sleep(100L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       }
    }

    public void run() {
        throw new RuntimeException();
    }
}

ThreadSampleクラスでは、通常キャッチする必要のないRuntimeExceptionを投げています。 例外が発生しても、プログラムは動作し続けてしまいます。 本来ならばスレッドを再び起動するなどの処理を行うべき箇所です。

「java.lang.Thread.UncaughtExceptionHandler」インタフェースは、 「java.lang.ThreadGroup」のuncaughtException()メソッドをインタフェース化したものです。 UncaughtExceptionHandlerは、キャッチされない例外によって指定されたスレッドが終了したときに呼び出される、 uncaughtException()メソッドのみを持つインタフェースです。 ThreadGroupを作成するより、簡単に例外を取得出来ます。

ThreadGroupを作成する方法

1
2
3
4
5
6
7
ThreadGroup group = new ThreadGroup("group"){
    public void uncaughtException(Thread t,Throwable e) {
        e.printStackTrace();
    }
};
Thread thread = new Thread(group,new ThreadSample());
thread.start();

このように、ThreadGroupを作成し、uncaughtException()メソッドをオーバーライドする必要がありました。 JDK1.5よりこのuncaughtException()メソッドがインタフェース化し、ThreadGroupを作成せずにすむようになりました。

Thread.UncaughtExceptionHandlerを実装したハンドラを、 ThreadのsetUncaughtExceptionHandler()メソッドで登録します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public void ThreadSample implements Runnable{
    public static void main(String[] args) {
        Thread thread = new Thread(new ThreadSample());
        thread.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        ...
}
import java.lang.Thread.UncaughtExceptionHandler;

public class MyUncaughtExceptionHandler implements UncaughtExceptionHandler {
    public void uncaughtException(Thread thread, Throwable throwable) {
        //アプリケーションの終了
        System.out.println("例外が発生しました。");
        throwable.printStackTrace();
        System.out.println("スレッドを再起動します。");
        thread.start();
    }
}

ここではスタックトレースを出力した上で、スレッドの再起動を行っています。 このサンプルは次のように出力されます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
例外が発生しました。

```java
java.lang.RuntimeException
    at ThreadSample.run(ThreadSample.java:41)
    at java.lang.Thread.run(Unknown Source)
```

スレッドを再起動します。
...

6.4. スレッドの状態の取得

java.lang.Threadに新たに状態を表す列挙形のサブクラスが追加されました。

Threadの状態は次の6つです。

  • NEW
    起動していないスレッドの状態
  • RUNNABLE
    Java 仮想マシンで実行されているスレッドの状態
  • BLOCKED
    ブロックされ、モニターロックを待機しているスレッドの状態
  • WAITING
    ほかのスレッドが特定のアクションを実行するのを無期限に待機しているスレッドの状態
  • TIMED_WAITING
    指定された待機時間、ほかのスレッドがアクションを実行するのを待機しているスレッドの状態
  • TERMINATED
    終了したスレッドの状態
    java.lang.Thread.getState()メソッドで取得します。
1
System.out.println(Thread.currentThread().getState());

出力結果 RUNNABLE

6.5. 一ミリ秒未満のスリープ

Thread.sleep()に新たなメソッドが追加されました。

1
    public static void sleep(long millis,int nanos)

第一引数でスリープするミリ秒を指定し、第二引数でスリープするナノ秒数を指定します。 第一引数と第二引数の合計数スレッドが停止します。 指定するナノ病は0潤オ999999の範囲内である必要があります。範囲外の場合、例外が発生します。

1
Thread.sleep(1000,1000);

また現在のLinux版JDKでは、1ミリ秒未満のスリープは意味がありません。