JDK 1.1 開発ガイド (Solaris 編)

推奨されないスレッドメソッド

Thread.stopThread.suspendThread.resume メソッドは、JDK 1.1 では推奨されません。Thread.stop は、本質的に安全でないためです。スレッドを停止すると、スレッドがロックしたモニターをすべてロック解除してしまいます。ThreadDeath 例外がスタックを上に移動した場合に、モニターのロックは解除されます。これらのモニターによって以前に保護されたオブジェクトが不整合の状態になっている場合、その他のスレッドはこれらのオブジェクトが不整合の状態にあるとみなします。そのようなオブジェクトは破損しているとみなされます。

破損しているオブジェクトに対して動作するスレッドは、明示的または非明示的に不特定に動作します。検査が行われない他の例外とは異なり、ThreadDeath はこのようなスレッドを警告メッセージを表示せずに停止します。このように、プログラムが破損しているということを示す警告が、ユーザーには何も通知されません。その後予期しない時に、プログラムが破損していることがわかります。

Thread.stop を、より安全にスレッドを終了させるコードに置き換えてください。ほとんどの stop() は、対象となるスレッドの実行を停止するかどうかを判定する変数を変更するコードに置き換えることができ、また置き換える必要があります。そのスレッドは、定期的にその変数を検査するようにする必要があります。スレッドを停止するようにその変数が示している場合は、スレッドは通常どおりに run() メソッドから返るようにします。たとえば、次のような start()stop()run() メソッドがアプレットに含まれているとします。


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 {
				Thread.sleep(interval);
			}
			catch (InterruptedException e){
			}
			repaint();
		}           
	}

アプレットの stop() および run() メソッドを以下のように変更することによって、Thread.stopを使用しなくて済みます。


public void stop() {
		blinker = null;
}            
public void run() {
		Thread thisThread = Thread.currentThread();
		while (blinker == thisThread) {
			try {
				Thread.sleep(interval);
			}
			catch (InterruptedException e){
			}
			repaint();
		}
}

また、Thread.suspend は、本質的にデッドロックを引き起こす可能性があるため、推奨されません。この Thread.resume も、他のコードに書き換える必要があります。スレッドが中断された時に、そのスレッドが重要なシステム資源を保護するモニターをロックしていると、ターゲットスレッドの実行が再開されるまで、他のスレッドはこのシステム資源にアクセスすることはできません。ターゲットの実行を再開するスレッドが Thread.resume を呼び出す前にモニターをロックしようとすると、デッドロックが発生します。

このようなデッドロックが発生すると通常はプロセスがフリーズして応答しなくなるので、デッドロックが発生したことがわかります。前述の Thread.stop の場合と同様に、適切なスレッドの状態 (active または suspended) を示す変数をスレッドに持たせる必要があります。スレッドが中断されたときには、スレッドは Object.wait を使用して待機します。スレッドが再開されたときには、Object.notify によってスレッドが再開されたことがターゲットスレッドに通知されます。たとえば、以下のように blinker というスレッドの状態を切り替えるイベントハンドラ mousePressed が、アプレットに含まれているとします。


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 と同じ節の中に wait() メソッドを置くこともできます。スレッドが再開された時にウィンドウがただちに再描画 (repaint ) されるように、checksleep よりも後に置いてください。たとえば次のように run() を記述します。


public void run() {
		while (true) {
			try {
				Thread.sleep(interval);
				synchronized(this) {
					while (threadSuspended)
						wait();
				}
			}
			catch (InterruptedException e){
			}
			repaint();
		}
} 

notify()mousePressed() メソッドの中にあり、run() メソッドの中の wait()synchronized ブロック中にあることに注意してください。これは Java 言語で必要とされていることで、wait()notify() が適切にシリアライズされるようにします。つまり、中断されたスレッドが notify() を認識できずに中断したままになる可能性がある競合状態が発生するのを防ぎます。