funini.com kei Java Thread

Thread

Javaでマルチスレッド

まずはサンプル

まずマルチスレッドのサンプルですが、
[ThTest.java]
class Runner implements Runnable{
  public void run() { 
    for(int i = 0; i < 3; i++) System.out.println("count :" + i);
  }
}

public class ThTest { public static void main(String[] args){ Runner runner = new Runner(); Thread t = new Thread(runner); t.start(); } }

ま、こんな感じかな。 Runnerのrun()が実際に走るスレッドになります。 mainの方はまとめると

  public static void main(String[] args){
    (new Thread(new Runner())).start();
  }

という荒っぽいこともできます。

Synchronizedの話題

Javaのオブジェクトの多くは、そのまま書いただけでは マルチスレッドで走らせると、複数のメソッドが同時に動いたりしてバグります。 だから、synchronizedを指定して「ここにアクセスするのは同時に一つだけ」と 指定します。
Vectorなど元々スレッドセーフなクラスを使うときは、 一つのメソッドが同時に呼ばれても問題はないようになってい(ると思い)ますが、 ArrayListなどスレッドセーフになっていないものは明示的に指定する必要があります。 さらに、プログラムの要請からsynchronizedを使って独自の条件を指定したりまできます。
例えば、ここの結晶のアプレットでは 結晶の座標を計算するスレッドとアニメーションを描画するスレッドがあり、 計算スレッドが計算した結果を待ち行列(キュー)に入れていき、 アニメーションスレッドはそこから取り出す、という作業をしています。 ここで、キューは同時に挿入と削除が行われる可能性があり、しかも 削除の方はデータが空のときは計算されるまで待たせる必要があります。
この条件を満たすクラスとして、以下のものを書いてみました。
[MyQueue.java]
import java.util.List;
import java.util.ArrayList;

public class MyQueue{ private List q = new ArrayList();

public void enqueue(Object o){ synchronized(q){ q.add(o); q.notify(); } }

public Object dequeue() { synchronized(q){ while(q.isEmpty()) try{ q.wait();} catch (InterruptedExceprion e){} return q.remove(0); } } }

synchronized(q)というのは、「このブロック内ではqに同時に複数アクセスできない」 ということを指定します。 ここで、複数の同じオブジェクトを対象とするsynchronizedでも、それぞれは連携して 動作し、同時に一つのブロックしか動きません。 例えばenqueue()とdequeue()が同時に呼ばれても、実際にqに触ることが出来るのは 片方だけで、もう片方はその直前でブロックされます。
しかし、この機能だけだと、dequeueの方で「キューが空の間は、データが入るまで待つ」 という処理が書けないので、wait()とnotify()という仕組みが用意されています。 synchronizedブロックの中でwait(100)という風に呼ぶと、最小100m秒間だけ、そのsynchronized ブロックのロックが解除されます。そこで、

 while(q.isEmpty()) q.wait(100);

という風に書くと、100m秒おきにキューが空でないかチェックし、空でなくなったら 次の処理に移ることができるようになっています。
また、ここで

 while(q.isEmpty()) q.wait();

というふうに書くと、スレッドは永久にブロックされます。 って、本当に永久にブロックされたら困るわけで、このブロックを起こす手段がちゃんと用意されています。 それがq.notify()というメソッドで、どこか別のブロックでこのnotifyが呼ばれると、 waitは解除されます。ここでは、enqueueの方でnitify()してるので、 dequeueのwaitはキューが空でなくなったら解除されます。
一応、このキューのテストプログラム。

[Qtest.java]
import dla.MyQueue;

class Producer implements Runnable{
  MyQueue q;
  Producer(MyQueue _q){q = _q;}
  public void run() {
    for(int i = 0; i < 3; i++){
      q.enqueue("Nexus" + i);
      try{
        Thread.sleep(1000); 
      } catch (Exception e){}
    }
  }
}

class Consumer implements Runnable{
  MyQueue q;
  public Consumer(MyQueue _q){q = _q;}
  public void run() {
    for(int i = 0; i < 3; i++){
      try{
        System.out.println((String)q.dequeue());
        Thread.sleep(1);
      } catch (Exception e){}
    }
  }
}

public class Qtest { 
  public static void main(String[] args){
    MyQueue q = new MyQueue();
    (new Thread(new Producer(q))).start();
    (new Thread(new Consumer(q))).start();
  }
}

データを入れるほうは1000msecおきにenqueueし、取り出すほうは1msecおきにdequeueするから、 waitが何度も呼ばれている計算になります。