Java : Generator
PythonのGeneratorみたいなものをJavaで作ってみようと思った。Javaには本物のマルチスレッドがあるのだから、Generatorくらいは当然作れるのだけれど、いざちゃんと協調動作させようとすると上手く書けなくてあきらめる人とか多いのじゃないかとか思ったので。
実装上は2つのスレッドを協調動作させているのだけれど、使っている側からは1つのスレッドが交互に制御を移しているような感覚で使えます。
これがサンプルコード。
public class GenMain{ public static void main (String[] args) { Generator<String> g = new Generator<String>(new Call()); String s = "開始"; i = 0; do{ System.err.println(i); System.err.println(s); i = 0; }while (null != (s = g.call())); System.err.println(i); } private static int i; private static class Call implements Generator.Call<String>{ public String call (Generator.Cont<String> c){ i += 2; c.yield("最初"); i += 3; c.yield("2つ目"); i += 4; c.yield("最後"); i += 5; return null; } } }
出力はこんな感じ。
$ java GenMain 0 開始 2 最初 3 2つ目 4 最後 5
Generatorのソースは次のとおり。最後まで実行しないと内部スレッドが回収されないので、shutdown()メソッドとか追加して、使い終わった後に内部スレッドの破棄を行えるようにしておくべきかと思う。
public class Generator<A>{ public interface Call<A>{ A call (Cont<A> c); } public interface Cont<A>{ void yield (A o); } public Generator (final Call<A> c){ this.mutex = new Object(); this.t = new Thread (){ public void run (){ yield_to(true); ret = c.call(new ContImpl()); synchronized (mutex){ is_main = true; mutex.notify(); mutex = null; } } }; synchronized (mutex){ t.start(); yield_to(false); } } private final Thread t; private volatile A ret = null; private volatile Object mutex; private volatile boolean is_main = true; private void yield_to (boolean b){ if (null == mutex) throw new IllegalStateException(); synchronized (mutex){ is_main = b; mutex.notify(); while (b == is_main) try{ mutex.wait(100);} catch(InterruptedException e){} } } public boolean finished (){ return mutex == null; } public A call (){ if (null == mutex) throw new IllegalStateException(); synchronized (mutex){ if (!is_main) throw new IllegalStateException(); } yield_to(false); return ret; } private class ContImpl implements Cont<A>{ public void yield (A o){ if (null == mutex) throw new IllegalStateException(); synchronized (mutex){ if (is_main || (!Thread.currentThread().equals(t))) throw new IllegalStateException(); } ret = o; yield_to(true); } } }