スレッド通信
通信による相互排他制御
スレッドにおいて同期をとることで排他制御をとることができました
この排他制御は、オブジェクトがロックを取得したり解放することで実現していました
スレッドの通信は、さらに同期を制御します
あるオブジェクトがロックを取得し、別のスレッドがメソッドの実行を待機しているとします
このとき、実行中のスレッドを一時的に待機させ、待機中のスレッドを走らせることができます
この一時的待機は、スレッドがロックを解放するわけではありません
一時的に待機しているスレッドはウェイトリストで待機しています
呼び出されたスレッドが作業を終了すると、ウェイとリストから待機中のスレッドを呼び出さねばなりません
これを制御するのがwait()メソッドとnotify()メソッドです
wait()メソッドはsynchronizedが指定されているメソッドを実行中のロックを取得しているスレッドを待機させます
すなわち、wait()を実行するスレッドのオブジェクトはロックを保有している必要があります
wait()により待機中のスレッドは、ウェイトリストに追加されます
同時に、そのメソッドの実行を待機していた(ロックの解放を待っていた)スレッドが実行されます
複数ある場合はJVMが任意のスレッドを実行させます
wait()メソッドの書式は次の3つです
- wait() : notify()またはnotifyAll()で呼び出されるまで待機します
- wait(long m) : mミリ秒待機、もしくはnotify()などで呼び出されるまで待機します
- wait(long m , long n) : mミリ秒 + nナノ秒、もしくはnotify()などで呼び出されるまで待機します
次のプログラムは、メソッドの実行中にスレッドを一時的に待機させて
ロックの解放を待っていた別のスレッドを実行させます
class Test extends Thread {
static Neko neko;
public static void main(String args[]) {
neko = new Neko();
new Test().start();
new Test().start();
}
public void run() {
neko.tell();
}
}
class Neko {
int ch = 0;
synchronized void tell() {
if (ch < 1) {
try {
ch++;
System.out.println("Kitty on your lap");
Thread.sleep(1000);
wait(3000);
System.out.println("おはようございますご主人様");
}
catch (InterruptedException err) {
System.out.println(err);
}
}
else {
System.out.println("ひざの上の同居人");
}
}
}
最初に実行されたスレッドは、wait()メソッドにより待機状態に入ります
同時に、ロックの解放を待っていたもうひとつのスレッドがtell()を実行します
3000ミリ秒経過すると、自動的にウェイトリストで待機していた最初のスレッドが復帰します
次のような結果が得られました
Kitty on your lap
ひざの上の同居人
おはようございますご主人様
ウェイトリストで待機しているスレッドを、明示的に呼び出すのが notify() メソッドです
待機時間が指定されなかった wait() の場合、もしくは時間に関係なく呼び出す時に指定します
notify()が呼び出すのは、ロックを待機させているスレッドの1つだけです
待機中のスレッドが複数の場合はJVMが任意に呼び出します
スタックやキューのような、一定の順番ではないので注意してください
class Test extends Thread {
static Neko neko;
public static void main(String args[]) {
neko = new Neko();
new Test().start();
new Test().start();
}
public void run() {
neko.tell();
}
}
class Neko {
int ch = 0;
synchronized void tell() {
if (ch == 0) {
try {
ch++;
System.out.println("wait() メソッドで停止します");
Thread.sleep(1000);
wait();
Thread.sleep(1000);
System.out.println("待機状態を解除しました");
}
catch (InterruptedException err) {
System.out.println(err);
}
}
else {
System.out.println("待機を解除します");
notify();
}
}
}
notify()が実行されると、待機中のスレッドが再開されます
notify()をコメント化するなどして、削除して実行してみてください
先ほどと違って、wait()で待機中のスタックは永遠に実行されません
Ctrl + C キーで強制的にアプリケーションを終了させてください
notify()は、待機中のスレッドをひとつだけ再開させます
しかし、場合のよっては待機中のスレッドを全て再開させたい場合もあります
その場合はnotifyAll()を使用することで、通知することができます
notifyAll()が実行されると、ウェイトリストで待機中のスレッド全てに通知されます
class Test extends Thread {
static Neko neko;
public static void main(String args[]) {
neko = new Neko();
for(int i = 0 ; i < 5 ; i++) new Test().start();
}
public void run() {
neko.tell();
}
}
class Neko {
int ch = 0;
synchronized void tell() {
if (ch < 4) {
try {
ch++;
System.out.println("wait() メソッドで停止します");
Thread.sleep(1000);
wait();
Thread.sleep(1000);
System.out.println("待機状態を解除しました");
}
catch (InterruptedException err) {
System.out.println(err);
}
}
else {
System.out.println("notifyAll() で待機を解除します");
notifyAll();
}
}
}
このプログラムは、4つのスレッドが待機状態になります
最初のスレッドから4つ目までは、ifステートメントを真と評価してwait()を実行します
5回目はelseを実行し、notifyAll()によって待機中の全てのスレッドに通知します
結果は次のようになりました
wait() メソッドで停止します
wait() メソッドで停止します
wait() メソッドで停止します
wait() メソッドで停止します
notifyAll() で待機を解除します
待機状態を解除しました
待機状態を解除しました
待機状態を解除しました
待機状態を解除しました
スレッドの通信はsynchronizedメソッドによる同期の中で制御するものです
synchronizedである以上、ひとつのオブジェクトが同時にメソッドを走ることはできません
ひとつのスレッドが実行している間、他のスレッドはどうしたって同時に実行はされません
場合によって、スレッドの割り込みが可能ですが
その場合は InterruptedException 例外が発生するようになっています