wait と notify
上のほうで wait と notify について触れましたが、何も例示していなかったのでちょっと書いてみます。
wait と notify を利用したパターンで私がよく行なうのは、busy-wait なループの代わりに wait と notify を使用することです。
たとえば flag が true になるまで待つという busy-wait なループは以下のようになります。
while( !flag ) Thread.sleep( 500 );
上記の場合、500ms 毎に flag に対する検査処理が走り、CPUを無駄に消費します。
synchronized, wait を利用すると以下のように書き換えられます。
synchronized( flagObject ) { while( !flagObject.isWeak() ) flagObject.wait(); }
こうすることにより、他スレッドより flagObject が notify されるまで flagObject の検索処理は行なわれなくなります。
他スレッドでは、以下のようなコードによりフラグを設定したりします。flagObject のクラスにあるフラグ設定メソッド内に同様の処理があってもよいと思われます。そのへんはケースバイケースで。
synchronized( flagObject ) { flagObject.setWeak( true ); flagObject.notifyAll(); }
ここで notifyAll を使用しているのは、notify だと wait しているスレッドが複数あった場合、そのうちのどれか一つにしか通知が行かない(=一つのスレッドしか処理が再開されない)ためです。notifyAll は対象オブジェクトに対して wait しているすべてのスレッドを順次再開していきます(再開の順番は不定)。
ほかには、特定の系に対する処理を行なう場合に、すでに他のスレッドが処理を行なっている場合はスルーして、行なっていない場合は処理するといったことをできたりします。これはCPU命令のtest-and-setみたいなのを実現するメソッドをもつフラグのクラスを作って実現します。値の評価と設定をアトミックに処理するメソッドってことです。
以下のようなメソッドで実現します。
public synchronized boolean checkAndSet() { if( flag )// 他スレッドで処理中 return false; flag = true; return true; } public synchronized void reset() { flag = false; }
このメソッドを利用すると synchronized 節に入っているのはメソッド内のみに限定されるため、処理本体が長くても他のスレッドを止めることは無いという利点があります。
使用例は以下のような感じ。
if( flagObject.checkAndSet() ) { try { // 排他処理 } finally { flagObject.reset(); } }
これに wait や notify を組み合わせると、さらに幅を広げることが可能と思われます。あるスレッドは処理が行なえなければ処理をスルーして、違うスレッドでは処理を行なえるまで待機するというような。
※ソースコードは適当に書いたので文法とかまちがってるかも。とりあえず synchronized, wait, notify は便利だぞってことで(w
busy-wait なループを見つけたら、synchronized, wait, notify を使用した形に書き直すことを一考するのもよいかと。