|
Thread.stop
が推奨されないのはなぜですか。
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 メソッドから整然と返す必要があります。これは Java ソフトウェアのチュートリアルで常に推奨している方法です。停止要求の即時通信を確実にするには、変数が 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() { Thread thisThread = Thread.currentThread(); while (true) { try { thisThread.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 { thisThread.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.currentThread().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(); } }結果の
run
メソッドは次のようになります。
public void run() { while (true) { try { Thread.currentThread().sleep(interval); if (threadSuspended) { synchronized(this) { while (threadSuspended) wait(); } } } catch (InterruptedException e){ } repaint(); } }明示的な同期化を行わない場合は、threadSuspended を volatile に設定して、一時停止要求の通信が速やかに行われるようにしてください。
この状況を正すには、ターゲットスレッドを一時停止させる場合には、stop メソッドがターゲットスレッドの即時再開を保証する必要があります。ターゲットスレッドは、再開すると直ちに、それまで自分が停止していたことを認識し、適切な方法で自らを終了させなければなりません。結果として、run および stop メソッドは、これまで説明した動作を行うように見えます。
public void run() { Thread thisThread = Thread.currentThread(); while (blinker == thisThread) { try { thisThread.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
とほぼ同じです)。今回は Thread.destroy
を実装していませんが、これを推奨しないわけではありません。将来この実装を実現する予定です。Thread.destroy
には確かにデッドロックを発生させる傾向がありますが、プログラムが即座に終了するよりもデッドロックの危険を冒してもよい状況があるという論議があります。
Copyright © 1995-97 Sun Microsystems, Inc. All Rights Reserved. コメントの送付先: jdk-comments@java.sun.com |
Java ソフトウェア |