wait()/notify()Revised: Jan./27th/2003; Since: Apr./21th/2002
Object クラスのスレッドメソッド同期指定したコードを持つオブジェクトは、最大一つのスレッドによってロックが取得されて実行されます。ロックは同期指定された共有オブジェクト(モニター)のものが使われます。以前に紹介しましたが、ロック自体は全てのオブジェクトが持っているものです。
次の三つのメソッドは、 Object クラスで定義されていますので、任意のクラスで利用することが出来ます。このオブジェクトのロックを取得しているスレッドに対して、ロックを放棄させたり、再びロック探索状態に戻したりできます。
したがって、モニターの、同期指定されたコード内で使われます。
wait()public final void wait() throws InterruptedException public final void wait(long timeout) throws InterruptedException public final void wait(long timeout, int nanos) throws InterruptedException
他のスレッドがこのオブジェクトの notify() メソッドまたは notifyAll() メソッドを呼び出すまで、このオブジェクトのロックを取得しているスレッドを待機させます。このとき、スレッドはこのオブジェクトのロックとCPUなどのシステムリソースを放棄し、オブジェクトの待機プールに入ります。
引数でタイムアウト時間を設定できます。引数を1つとる場合は、指定した数字はミリ秒(10の-3乗秒)単位で解釈され、その時間が経過すれば自動的に復帰し始めます。引数を2つとる場合は、二つ目の数字がナノ秒間(10の-9乗秒間、10の-6乗ミリ秒間)と解釈されて、一つ目の数字で指定したミリ秒に加算された時間待機します。
// 最大 200*10^6 + 10 ナノ秒待機 wait(200, 10);
notify() が呼ばれるか、メソッド引数のタイムアウト時間が経過するかすると、他のスレッドと同じように、ロックの探索状態に入り、 JavaVM のスケジューラに再びロックを与えられてから実行を再開します。
sleep() の場合は指定した時間後に実行可能状態に戻し、実際に実行が再開されるのはスケジューラに許可された後になります。このため、細かい時間を指定しても、必ずしも指定時間後に再開される保障はありませんでした。wait() の場合も同様で、ナノ秒単位で最大待機時間を指定しても実際に再開されるのがいつになるのかは分かりません。それでも、タイムアウト時間を設定しないと、いつまでも待機しつづけてしまうデッド・ロックの原因になりかねませんので、メソッド引数でタイムアウト時間を設定しておいたほうが良いでしょう。
notify()public final void notify()
wait() メソッドによって待機中のスレッドを 1 つ再開します。言い換えると、当該オブジェクトの待機プール内の任意の一つのスレッドを再開します。
notifyAll()public final void notifyAll()
wait() メソッドによって待機中のスレッドをすべて再開します。言い換えると、当該オブジェクトの待機プール内のすべてのスレッドを再開します。
使い方は次項のサンプルを参照ください。
これらのメソッドは、同期指定されたコードを持つオブジェクトのロックを利用します。同期指定されたコードを持つオブジェクトのことをモニターと呼びます。
wait() メソッドは、モニターに対して呼ばれ、そのモニターのロックを取得しているスレッドに対して、ロックを放棄させてモニタの待機プールに入れます。このとき、待機したスレッドはCPUなどのマシンリソースも放棄します。
notify() メソッドは、モニターに対して呼ばれ、そのモニターの待機プール内の任意の一つのスレッドを再びロック探索状態に戻します。どのスレッドが再開するか、いつロックを取得して再開するかは指定できません。
notifyAll() メソッドは、モニターに対して呼ばれ、そのモニターの待機プール内のすべてのスレッドをロック探索状態に戻します。いつロックを取得して再開するかは指定できません。
// 同期指定された共有オブジェクトクラス(モニター)
class Share {
// 共有データ
private String msg;
public synchronized String read() {
// 読み込みの為にこのモニターのロックを取得したスレッドを待機させる
try {
wait();
} catch (InterruptedException e) {
System.out.println(e);
}
// 読み込み
return msg;
}
public synchronized void write(String str) {
// 書き込み
msg = str;
// 書き込み後にこのモニターの待機プール内のスレッドをロック探索状態にする
notifyAll();
}
}
このクラスをインスタンス化したオブジェクトは、道具として他のオブジェクトから呼ばれて使われます。このオブジェクトの read() メソッドを呼び出して使うスレッドは、 wait() メソッドによって問答無用でこのオブジェクトのロックを放棄させられて待機プールに入れられます。続いて、他のスレッドがこのオブジェクトのロックを取得して write() メソッドを呼び出すと、メソッド実行の最後に notifyAll() が実行されて、待機プール内のすべてのスレッドがロック探索状態に復帰します。
wait(), notify() は Share オブジェクトに記述してありますが、待機/復帰の影響を受けるのは、このオブジェクトのロックを取得しているスレッドであることに注意してください。