Java非推奨スレッド・プリミティブ
Thread.stopが非推奨になり、スレッドを停止する機能が削除されたのはなぜですか。
本来は安全ではないからです。 スレッドを停止すると、ロックされたすべてのモニターのロックが解除されました。 (ThreadDeath例外がスタックに伝播されるため、モニターはロック解除されました。) これらのモニターによって以前に保護されていたいずれかのオブジェクトが一貫性のない状態であった場合、他のスレッドはこれらのオブジェクトを一貫性のない状態で表示している可能性があります。 そのようなオブジェクトは、壊れたオブジェクトと呼ばれます。 壊れたオブジェクトに対してスレッドが操作を実行すると、予期しない結果になる可能性があります。 この動作は、微妙で検出が困難な場合と、はっきりと通知される場合があります。 他の未チェックの例外とは異なり、ThreadDeathはサイレントにスレッドを強制終了しました。したがって、ユーザーは、プログラムが破損している可能性があることを警告しませんでした。 破損は、実際の損傷が発生したあと、数時間または数日後に発生する可能性があります。
ThreadDeathを捕捉して破損したオブジェクトを修正することはできませんでしたか。
理論的には、おそらく可能です。ただし、正しいマルチスレッド・コードを記述するのは非常に複雑なタスクです。 これがほとんど実行不可能なタスクであることは、次の2つの理由によります。
- スレッドは
ThreadDeath例外をほぼどこでもスローできます。 このことを念頭に置いて、すべての同期化されたメソッドおよびブロックを詳細に調査する必要がある。 - スレッドは、最初の(
catchまたはfinally句)からのクリーンアップ中に2番目のThreadDeath例外をスローする可能性があります。 クリーンアップは、成功するまで繰り返す必要があります。 この動作を確実に行うコードは非常に複雑になる。
Thread.stopの代わりに何を使うべきですか
多くの場合、ターゲット・スレッドの実行停止を指示するには、stopではなく、単に一部の変数を変更するコードを使用する必要があります。 ターゲット・スレッドは、この変数を定期的に検査し、実行を停止するべきことを変数が示している場合には、スレッドのrunメソッドから通常の方法で復帰する必要があります。 stop-requestのプロンプト通信を保証するには、変数をvolatile (または変数へのアクセスを同期する必要があります)にする必要があります。
たとえば、アプレットに次のstart、stop、およびrunメソッドが含まれているとします。
private Thread blinker;
public void start() {
blinker = new Thread(this);
blinker.start();
}
public void stop() {
blinker.stop(); // UNSAFE!
}
public void run() {
while (true) {
try {
Thread.sleep(interval);
} catch (InterruptedException e){
}
repaint();
}
}
アプレットのstopおよびrunメソッドを次のコードと置き換えることによりThread.stopを使用せずに済みます。
private volatile Thread blinker;
public void stop() {
blinker = null;
}
public void run() {
Thread thisThread = Thread.currentThread();
while (blinker == thisThread) {
try {
Thread.sleep(interval);
} catch (InterruptedException e){
}
repaint();
}
}
長い間(入力などを)待機しているスレッドをどうすれば停止できますか
その目的には、Thread.interruptを使用します。 上と同じ「状態に基づいた」シグナル・メカニズムを使用できますが、状態変更(前の例ではblinker = null)のあとに、Thread.interruptを呼び出して待機状態に割り込むことができます。
public void stop() {
Thread moribund = waiter;
waiter = null;
moribund.interrupt();
}
この方法では、割込み例外をキャッチするがそれを処理する準備のできていないメソッドはその例外をただちに再宣言することが重要です。 ここで再スローではなく再宣言と書いたのは、例外をいつも再スローできるとはかぎらないからです。 InterruptedExceptionをキャッチしたメソッドが、この(確認済みの)例外をスローするように宣言されていない場合は、次のような決まった書き方により「自らに再割り込みする」必要があります。
Thread.currentThread().interrupt();
これにより、スレッドは、可能なかぎり早くInterruptedExceptionを再発行できるようになります。
スレッドがThread.interruptに応答しないとどうなりますか
アプリケーション独自の技法が使用可能な場合もあります。 たとえば、スレッドが既知のソケット上で待機している場合は、ソケットを閉じることによりスレッドをただちに復帰させることができます。 しかし、残念ながら、汎用的に使用できる技法はありません。 待機しているスレッドがThread.interruptに応答しないすべての状況では、そのスレッドはThread.stopにも応答しないことに注意してください。 そのような状況としては、意図的なサービス妨害攻撃や、thread.stopとthread.interruptが適切に機能しない入出力操作などがあります。