ここまでスレッドのサンプルを書いてきたのですが、主にプログラムがみにくくなるとか、めんどいとか、動くからえぇやんという理由で、やるべきことをやってないところがあります。
スレッドからのSwing操作とwaitの処理です。
Swingはシングルスレッドモデルで実装されているので、GUIスレッドとは別のスレッドからSwingを操作するべきではありません。EventQueue#invokeLaterなどを使って処理をGUIスレッドにのせる必要があります。(SwingUtilities#invokeLaterは内部でEventQueue#invokeLaterを呼び出しているだけです)
今回の一連のサンプルで使っているJTextField#setTextや、今回は使ってないけどよく使うJTextArea#appendなど、JTextComponentのテキスト操作に関しては、幸いスレッドセーフなのでそのまま使えますが、JFrame#setVisibleなどほとんどのSwingメソッドはスレッドセーフではありません。
mainメソッドからはじまるスレッドも、GUIスレッドとは違うので、今回のサンプルでは処理全体をinvokeLaterにしておくべきです。
たとえば処理全体をhogeメソッドに入れて
private static void hoge(){ //処理 }
このメソッドをinvokeLaterから呼び出します
EventQueue.invokeLater(new Runnable(){ @Override public void run(){ hoge(); } }
もうひとつ、waitの問題。
waitがnotifyがなくても再開することがあるという問題で、JavaDocではObject#waitでは「スプリアスウェイクアップ」、Condition#awaitでは「見せかけの起動」とかかれてます。
そのため、Object#waitやCondition#awaitは、再開条件でのループで囲む必要があります。
これはEffective Javaで指摘されたことにより有名になりました。そのため、JDK1.4までのJavaDocには記述がありません。
http://sdc.sun.co.jp/java/docs/j2se/1.4/ja/docs/ja/api/java/lang/Object.html#wait()
http://java.sun.com/javase/ja/6/docs/ja/api/java/lang/Object.html#wait()
ということで、LockConditionSampleでこの問題に対応します。
とりあえずフラグを用意します。ここでは匿名クラスで使うためにfinalをつけるので、配列にしてます。
//ロック用オブジェクト final Lock lock = new ReentrantLock(); final Condition condition = lock.newCondition(); final boolean[] flag = {false};
awaitメソッドの呼び出しをwhileで囲みます。
flag[0] = false; while(!flag[0]){ condition.await(); }
で、signalメソッド呼び出し時にフラグを立てます。
try{ lock.lock(); flag[0] = true; condition.signal(); }finally{ lock.unlock(); }
というか、これは各サンプルでちゃんとやっておこう。
全体のソース
続きを読む