|
Concurrency Utilities そのニ スレッドの拡張の巻 |
||||||||||
|
||||||||||
最初はかるくいきましょう。 例外処理で何か書かなくてはいけないときにまず何を書きますか? 私は Throwable#printStackTrace メソッドです。どのクラスのどの行が例外を起こして、それがどこから呼ばれているかを知ることができます。 これはこれで非常に便利なのですが、デバッグをしているときにスタックトレースを出力したくなることありませんか? 私はあります。 イベントのようにどこから呼ばれているかわからないものや、非同期に実行しているものなどの情報を知るのにスタックトレースは有用です。 スタックとレースを出力してくれるメソッドに Thread#dumpStack があります。static なメソッドなのでこれはこれで重宝するのですが、問題はコンソールにしか出力できないことです。 例外の場合、Throwable#printStackTrace(PrintStream s) メソッドと Throwable#printStackTrace(PrintWriter s) メソッドがあるので、ストリームに書き出しができるのですが、Thread#dumpStack メソッドではできません。 さて、どうしましょう。 Tiger では Thread クラスに 2 つのメソッドが追加されました。getStackTrace メソッドと getAllStackTraces メソッドです。 厳密にいえばこれらのメソッドは Concurrency Utilities には関係ないのですが、Thread クラスということでまとめてしまいました。 さっそく、使い方を見ていきましょう。
まずは getStackTrace メソッドからです。以下に示す部分は ThreadTest1 クラスのインナークラスの Task クラスです。単にスリープするだけのクラスですが。
赤い字で示した部分が getStackTrace メソッドを使用している部分です。多分、この使い方が一番多いと思います。 Thread#currentThread メソッドでカレントのスレッドを取得し、そのスレッドのスタックトレースを要求します。 Thread#getStackTrace メソッドの戻り値は StackTraceElement クラスの配列です。StackTraceElement クラスは J2SE 1.4 で導入されたクラスで、Chained Exception で使用されます。例外では一足早くスタックトレースをプログラム中で扱えるようになっていたわけです。 さて、この部分の後の for ループで StackTraceElement オブジェクトを出力しています。結果はこうなります。
よく見知った形ですね。StackTraceElement クラスについては J2SE 1.4 の Chained Exception Facility の解説を見てください。 さて、もう Thread#getAllStackTraces メソッドの方です。このメソッドの戻り値は Map インスタンスです。なんか変な感じですね。さっそく使ってみましょう。
getAllStackTraces メソッドは static メソッドなので、スレッドのインスタンスがなくても使用できます。 とりあえず、得られた Map オブジェクトを出力してみたました。
これを見るとだいたい分かりました。キーが Thread オブジェクトで、値が StackTaceElement の配列のようです。なぜ、配列か分かるかというと値の出力がシグネチャで行われており、配列の記号が使用されているからです。 つまり、[ が配列の記号になります。その後が配列の要素を示しいます。L の場合はクラスになり、その後にパッケージつきでクラス名が記されます。そして、クラス名の最後に ; が記述されます。 この出力からはスレッドが 5 つ走っており、その 1 つがメインスレッドだということです。そして、最後の Thread-0 が Task クラスを実行しているスレッドです。 そこで、メインスレッドのスタックトレースを出力してみましょう。
メインスレッドがカレントスレッドなのでキーには Thread#currentThread メソッドで取得できる Thread オブジェクトにしてあります。 あとは先ほどと同様に 1 つ 1 つ出力しただけです。出力結果はつぎのようになりました。
この機能を使えば、好きなときに好きなようにスレッドのスタックトレースを得ることができます。それもカレントスレッド以外のすべてのスレッドのスタックトレースもです。 デバッグに限らず、ソフトウェアの管理にも使えそうです。たとえば、定期的にスレッドのスタックトレースをログに出力し、死んでいるスレッドがないか、へんなところで処理が止まっているスレッドがないかなどを調べることができます。 なかなか使いでがありそうですね。 |
|
|||||||||||
さて、ここからが本題です。 その一の最後に書いたことが気にかかっていることなのです。くり返しになりますが、もう一度書いておきましょう。 問題は RuntimeException に関することです。RuntimeException は通常の Checked Exception と違って try ... catch を書く必要がありません。 try ... catch を書かないので、当たり前ですがキャッチする人はいません。シングルスレッドのプログラムでキャッチされない RuntimeException が発生すると、プログラムはスタックトレースを出力して止まってしまいます。 それでは、マルチスレッドで動いている場合はどうなるのでしょう。たとえば次のサンプルを見てください。
このサンプルはメインスレッドと異なるスレッドで RuntimeException を発生させます (かなり意図的ですが)。メインスレッドは無限ループになります。
実行してみると次のようになります。
テキストだけだと分かりにくい思いますが、例外が発生してもプログラムは止まりません。ずっと動きつづけています (スリープしているだけですが)。 もしこれが javaw コマンドで実行されたり、コマンドプロンプトではないところから実行されたらどうでしょう。スタックトレースを出力するところはないので、例外が起きたかどうか分かりません。その上、アプリケーションはそのまま動きつづけてしまうのです。 このようにキャッチされない例外が発生すると、そのスレッドは止まってしまいます。しかし、他のスレッドはそのまま動きつづけます。 たとえば、スレッドプールのように複数のスレッドが複数のタスクを処理するような場合、RuntimeException が発生してもだれも気がつかないかもしれません。 それではどうすればいいのでしょうか。J2SE 1.4 までは ThreadGroup クラスを使います。 RuntimeExceptionTest1 クラスを ThreadGroup クラスを使うように変更したのが RuntimeExceptionTest1_1 クラスです。
UncaughtExceptionTest1_1 クラスでは ThreadGroup クラスの派生クラスの MyThradGroup クラスを使用します。
uncaughtException メソッドをオーバロードしています。ここがミソです。 生成するスレッドはこの MyThreadGroup クラスで管理させます。
実行した結果は次のようになりました。
無限ループで止まらないのは同じですが、異なるのは MyThreadGroup クラスの uncaughtException クラスで RuntimeException の例外処理を行っている点です。 プログラムの中で、キャッチできなかった RuntimeException をこのメソッドでキャッチできるのです。キャッチできればこっちのものです。ログに書いたり、落ちてしまったスレッドを起動しなおしたりという例外処理を行うことができます。 でも、わざわざこれだけのためにクラスを 1 つ作らなくてはいけないのも考えものです。また、Executors クラスが生成するスレッドプールにスレッドグループを割り当てることはできません。 そこで、後づけできるハンドラの登場です。
|
|
||||||||
キャッチできなかった例外をキャッチするために Tiger では、UncaughtExceptionHandler インタフェースという新しいインタフェースを導入しました。 UncaughtExceptionHandler インタフェースでは uncaughtException メソッドが定義されています。今まで ThreadGroup クラスを使用していたときと同じように記述することができます。現に ThreadGroup クラスはこのインタフェースをインプリメントするように変更されています。 Thread クラスには後から UncaughtExceptionHandler インタフェースを設定するためのメソッドが 2 つ追加されています。
前者がデフォルトの UncaughtExceptionHandler を設定するもの、後者がスレッドのインスタンスごとに UncaughtExceptionHandler を設定するメソッドになります。 さっそく、このインタフェースを使ってサンプルを作ってみましょう。
まず UncaughtExceptionHandler インタフェースをインプリメントしたクラスを作っておきます。
2 つ作ったのは、片方をデフォルトにしたときに区別がつけられるようにです。 UncaughtExceptionHandler インスタンスを設定するところは次のようにになります。
さて、実行するとどうなることやら。
おかしいですね。setUncaughtExceptionHandler で設定した UEHandler2 オブジェクトが使用されていません。 デフォルトを設定しないなど試してみましたのですが、結局 setUncaughtExceptionHandler メソッドでは設定できませんでした。 正式版では直ることを期待しましょう。
|
|
||||
少なくともデフォルトの UncaughtExceptionHandler インスタンスを設定できるようになれば、Executors クラスが生成したスレッドプールでも Uncaught Exception をキャッチすることが可能になります。 ただ、これだとすべてのスレッドの例外処理を 1 つのクラスに集約してしまうので、例外処理的にはちょっとやりにくくなってしまいます。 はやく直らないかなぁ。
今回使用したサンプルはここからダウンロードできます。 参考
(Feb. 2004) |
|