まずマルチスレッドのサンプルですが、
[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();
}
という荒っぽいこともできます。
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が何度も呼ばれている計算になります。
[an error occurred while processing this directive]