あなたは仕組みを誤解してwait()
います。オブジェクトを呼び出しwait
ても、Thread
そのスレッドは一時停止しません。代わりに、現在実行中のスレッドに、何か他のことが起こるのを待つように指示します。その理由を説明するには、少しバックアップして、synchronized
実際に何が行われるかを説明する必要があります。
ブロックに入ると、オブジェクトに関連付けられたモニターsynchronized
を取得します。例えば、
synchronized(foo) {
オブジェクトに関連付けられたモニターを取得しますfoo
。
モニターを取得すると、同期ブロックを終了するまで、他のスレッドはそれを取得できません。これがどこにwait
あり、notify
入ってきます。
wait
保持しているモニターを一時的に解放するように現在実行中のスレッドに指示する Object クラスのメソッドです。これにより、他のスレッドが で同期できるようになりますfoo
。
foo.wait();
このスレッドは、他の誰かが呼び出しnotify
またはnotifyAll
オンにするfoo
(またはスレッドが中断される) まで再開されません。それが発生すると、このスレッドはモニターの再取得を試みてからfoo
続行します。他のスレッドがモニターの取得を待機している場合、それらのスレッドが最初に取得される可能性があることに注意してください。JVM がロックを配布する順序の保証はありません。wait()
誰もnotify
またはを呼び出さない場合、 は永久に待機することに注意してくださいnotifyAll
。通常wait
は、タイムアウトを取る別の形式を使用することをお勧めします。そのバージョンは、誰かが呼び出したとき、notify
またはnotifyAll
タイムアウトになったときに起動します。
したがって、待機を行うスレッドと通知を行う別のスレッドが必要です。wait
との両方notify
が、待機または通知しようとしているオブジェクトにモニターを保持する必要があります。これが、IllegalMonitorStateException が表示される理由です。
次の例が理解に役立ちます。
class RepaintScheduler implements Runnable {
private boolean paused = false;
private final Object LOCK = new Object();
public void run() {
while (true) {
synchronized(LOCK) {
if (paused) {
try {
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
repaint();
}
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void pause() {
synchronized(LOCK) {
paused = true;
LOCK.notifyAll();
}
}
public void resume() {
synchronized(LOCK) {
paused = false;
LOCK.notifyAll();
}
}
}
次に、アプレット コードで次の操作を実行できます。
public void init() {
RepaintScheduler scheduler = new RepaintScheduler();
// Add listeners that call scheduler.pause and scheduler.resume
btn_increment.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {
scheduler.resume();
}});
btn_decrement.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {
scheduler.pause();
}});
// Now start everything up
Thread t = new Thread(scheduler);
t.start();
}
Applet クラスは、スケジューラが一時停止/再開する方法を気にせず、同期ブロックを持たないことに注意してください。
したがって、ここで考えられる一連のイベントは次のとおりです。
- スレッド A が再描画スケジューラの実行を開始します。
- スレッド A は 20 ミリ秒スリープ状態になります。
- スレッド B (イベント ディスパッチ スレッド) はボタン クリックを受け取ります。「一時停止」を呼び出します。
- スレッド B は LOCK でモニターを取得します。
- スレッド B は「一時停止」変数を更新し、LOCK.notifyAll を呼び出します。
- LOCK を待機しているスレッドがないため、興味深いことは何も起こりません。
- スレッド B は LOCK でモニターを解放します。
- スレッド A が起動し、再びループを通過します。
- スレッド A は LOCK でモニターを取得します。
- スレッド A は一時停止する必要があることを確認したため、LOCK.wait を呼び出します。
- この時点でスレッド A は一時停止し、誰かが notifyAll を呼び出すのを待ちます。スレッド A は LOCK でモニターを解放します。
- しばらくして、ユーザーは「再開」をクリックします。
- スレッド B が scheduler.resume を呼び出します。
- スレッド B は LOCK でモニターを取得します。
- スレッド B は「一時停止」変数を更新し、LOCK.notifyAll を呼び出します。
- スレッド A は「notifyAll」を見て、ウェイクアップします。LOCKでモニターを取得しようとしますが、スレッドBによって保持されているため、スレッドAがブロックされます。
- スレッド B は LOCK でモニターを解放します。
- スレッド A はモニターを取得して続行します。
それはすべて意味がありますか?
個別の LOCK 変数を持つ必要はありません。Thread
インスタンスで待機/通知を呼び出していないという事実を強調するために、これを行いました。同様に、RepaintScheduler 内のロジックは理想的ではありませんが、wait/notify の使用方法を説明するためのものです。