Thread.stop
が非推奨なのはなぜですか本質的に安全ではないからです。スレッドを停止すると、そのスレッドがロックしたすべてのモニターのロックが解除されます。(ThreadDeath
例外がスタックまで伝わると、モニターのロックが解除される。)これらのモニターによって以前保護されていたオブジェクトが整合性のない状態になると、ほかのスレッドも、これらのオブジェクトが整合性のない状態にあるとみなします。そのようなオブジェクトは、壊れたオブジェクトと呼ばれます。壊れたオブジェクトに対してスレッドが操作を実行すると、予期しない結果になる可能性があります。この動作は、微妙で検出が困難な場合と、はっきりと通知される場合があります。ほかの非チェック例外とは異なり、ThreadDeath
は、スレッドをそのまま強制的に終了します。このため、ユーザーは、プログラムが壊れる可能性を警告されることがありません。プログラムが壊れていることは、実際に損傷を受けたしばらくあとに明らかになり、それが数時間後または数日後になることもあります。
ThreadDeath
例外をキャッチし、壊れたオブジェクトを修復することはできないのですか理論的には、おそらく可能です。ただし、正しいマルチスレッド・コードを記述するのは非常に複雑なタスクです。これがほとんど実行不可能なタスクであることは、次の2つの理由によります。
ThreadDeath
例外をほとんどすべての場所でスローする。このことを念頭に置いて、すべての同期化されたメソッドおよびブロックを詳細に調査する必要がある。catch
またはfinally
節)に、2番目のThreadDeath
をスローすることがある。クリーンアップは、正常に終了するまで繰返し実行される必要がある。この動作を確実に行うコードは非常に複雑になる。Thread.stop(Throwable)
についてはどうですか上で説明した問題すべてに加えて、このメソッドは、対象とするスレッドの処理準備ができていないという例外(このメソッドがなかったならスレッドがスローできないはずの、チェックされる例外を含む)を発生させるのに使用されることがあります。たとえば、次のメソッドの動作は、Javaのthrow
操作と同じですが、呼出し側のメソッドがスローする可能性のあるチェックされる例外すべてが宣言されていることを保証しようとするコンパイラをだまして失敗させます。
static void sneakyThrow(Throwable t) { Thread.currentThread().stop(t); }
Thread.stop
の代わりに何を使うべきですか多くの場合、ターゲット・スレッドの実行停止を指示するには、stop
ではなく、単に一部の変数を変更するコードを使用する必要があります。ターゲット・スレッドは、この変数を定期的に検査し、実行を停止するべきことを変数が示している場合には、スレッドのrunメソッドから通常の方法で復帰する必要があります。停止要求の即時通信を確実にするには、変数を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が適切に機能しない入出力操作などがあります。
Thread.suspend
およびThread.resume
が非推奨なのはなぜですかThread.suspend
は、本質的にデッドロックを起こす傾向があります。ターゲット・スレッドが、中断される時点で、重要なシステム・リソースを保護するモニターをロックしている場合、ターゲット・スレッドが再開されるまでどのスレッドもそのリソースにアクセスできません。このとき、ターゲット・スレッドを再開するスレッドが、resume
を呼び出す前にこのモニターをロックしようとすると、デッドロックが発生します。通常、このようなデッドロックは、プロセスの「凍結」により明らかになります。
Thread.suspend
とThread.resume
の代わりに何を使うべきですかThread.stop
の場合と同様、賢明なアプローチは、スレッドの望ましい状態(実行中または中断中)を示す変数を「ターゲット・スレッド」にポーリングさせることです。望ましい状態が中断中である場合、スレッドはObject.wait
を使用して待機します。スレッドが再開されたときは、ターゲット・スレッドはObject.notify
を使って通知を受けます。
たとえば、アプレットに次のようなmousePressedイベント・ハンドラが含まれており、それがblinker
というスレッドの状態を切り替えるとします。
private boolean threadSuspended; Public void mousePressed(MouseEvent e) { e.consume(); if (threadSuspended) blinker.resume(); else blinker.suspend(); // DEADLOCK-PRONE! threadSuspended = !threadSuspended; }上のイベント・ハンドラを次のコードで置き換えると、
Thread.suspend
およびThread.resume
を使わなくて済みます。
public synchronized void mousePressed(MouseEvent e) { e.consume(); threadSuspended = !threadSuspended; if (!threadSuspended) notify(); }そして次のコードを「実行ループ」に追加します。
synchronized(this) { while (threadSuspended) wait(); }
wait
メソッドは、InterruptedException
をスローするため、このメソッドをtry ... catch
節の内部に置く必要があります。このメソッドをsleep
と同じ節に入れると効果的です。チェックはsleep
の前ではなくあとに行われるので、スレッドが「再開」されるとただちにウィンドウが再描画されます。このように記述したrun
メソッドは、次のようになります。
public void run() { while (true) { try { Thread.sleep(interval); synchronized(this) { while (threadSuspended) wait(); } } catch (InterruptedException e){ } repaint(); } }
mousePressed
メソッドのnotify
およびrun
メソッドのwait
は、synchronized
ブロックの内部にあることに注目してください。これは言語の文法で要求されているからだけでなく、wait
およびnotify
が適切に直列化されることを保証します。この場合は、これにより競合状態が回避されます。つまり、「中断された」スレッドがnotify
を検出できずに、ずっと中断されたままになる事態を避けられます。
Javaにおいて同期化に要するコストは、プラットフォームが成熟するにつれて減少していますが、まったくなくなることはありません。簡単な技法を使用して、上の「実行ループ」の反復処理に追加した同期処理を省くことができます。追加された同期ブロックは、スレッドが実際に中断されている場合にのみ同期ブロックを入力する、わずかに複雑なコードに置き換えられます。
if (threadSuspended) { synchronized(this) { while (threadSuspended) wait(); } }
明示的な同期化を行わない場合は、threadSuspendedをvolatileに設定して、一時停止要求の通信がすみやかに行われるようにしてください。
結果のrun
メソッドは次のようになります。
private volatile boolean threadSuspended; public void run() { while (true) { try { Thread.sleep(interval); if (threadSuspended) { synchronized(this) { while (threadSuspended) wait(); } } } catch (InterruptedException e){ } repaint(); } }
この状況を正すには、stopメソッドで、ターゲット・スレッドが中断されている場合に、そのスレッドをただちに再開する処理を確実に実行しなければなりません。ターゲット・スレッドは、再開したのち、自身が停止されたことをただちに認識して、適切な方法で終了しなければなりません。そのような処理を加えたrunおよびstopメソッドは、次のようになります。
public void run() { Thread thisThread = Thread.currentThread(); while (blinker == thisThread) { try { Thread.sleep(interval); synchronized(this) { while (threadSuspended && blinker==thisThread) wait(); } } catch (InterruptedException e){ } repaint(); } } public synchronized void stop() { blinker = null; notify(); }stopメソッドでThread.interruptを呼び出す場合(以前に説明した方法)は、notifyを呼び出す必要はありませんが、同期化は行う必要があります。それにより、競合状態のためにターゲット・スレッドが割込みを検出し損ねることを防げます。
Thread.destroy
についてはどうですかThread.destroy
は、これまで実装されたことがなく、非推奨になりました。もし実装されたとすると、Thread.suspend
の場合と同様に、デッドロックを発生させやすい傾向があります。実際、Thread.resume
が続く可能性のないThread.suspend
とほとんど同じです。
Runtime.runFinalizersOnExit
が非推奨なのはなぜですかさらに、その呼出しは、VMグローバル・フラグを設定するという意味で、「スレッドに対して安全」ではありません。これは、ファイナライザを持つあらゆるクラスを、ライブ・オブジェクトのファイナライズに対して防御するようにコーディングしなければならないことにつながります。