正しいスレッドプログラム

ここまでスレッドのサンプルを書いてきたのですが、主にプログラムがみにくくなるとか、めんどいとか、動くからえぇやんという理由で、やるべきことをやってないところがあります。
スレッドからの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();
}


というか、これは各サンプルでちゃんとやっておこう。


全体のソース

続きを読む