マルチスレッドプログラミングのパターンの一つにFutureパターンというものがある。
これは、ある処理を別スレッドで非同期に実行させて、その結果を受けたいときに用いられるパターンである。
特徴的なのは、処理の実行担当者(JavaではExecutorServiceがそれにあたる)は、処理(JavaではCallable)が渡されると別スレッド上で処理を開始して、メインスレッドには即座にFutureオブジェクトを返すことである。
なぜこのオブジェクトがFutureと呼ばれるかというと、今現在はまだ結果を取得できないが、将来のある時点で取得することになるからである。
その後、Futureのget()メソッドを呼ぶと、メインスレッドはCallableの処理が終わるまでブロックされる。
そして別スレッドで処理が終わった時点で結果が取得できる。
プログラム例を以下に示す。
public static void main(String[] args) { ExecutorService es = Executors.newSingleThreadExecutor(); // submit()は、別スレッド上でCallableの処理を開始し、 // メインスレッドには即座にFutureオブジェクトを返す Future<String> future = es.submit(new Callable<String>() { // 別スレッドで実行する処理 @Override public String call() throws Exception { System.out.println("Callable start"); // ・・・何らかの重い処理。 // 5秒待つ Thread.sleep(5000); return "result ok."; } }); System.out.println("Callable submit."); String result = ""; try { // Callableの処理が終わり、結果が返ってくるまでブロックされる。 result = future.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } System.out.println("result=" + result); }
このプログラムを実行すると
Callable submit. ・・・①
Callable start ・・・ ②
result=result ok. ・・・ ③
と表示される。
最初に①と②が表示された後、5秒後に③が表示される。
もしかすると、①と②の表示順番は逆転するかも知れない。
ちなみに、C#で同じ事をやろうとすると以下のようになるのではないかと思う。
delegate String DoAsync(); private void button1_Click(object sender, EventArgs e) { DoAsync da = () => { Console.WriteLine("async method start."); System.Threading.Thread.Sleep(5000); return "result ok."; }; IAsyncResult ar = da.BeginInvoke(null, null); Console.WriteLine("BeginInvoke called."); String result = da.EndInvoke(ar); Console.WriteLine(result); }
実行すると、始めに
BeginInvoke called.
async method start.
と表示された後、5秒後に
result ok.
と表示されるだろう。
C#の場合はデリゲートを非同期で実行できるので、多少すっきりと書くことが出来る。ただし、このやり方だと必ず標準のスレッドプールで実行される。
対して、Javaの場合はExecutors#newSingleThreadExecutor()の引数にThreadFactoryを渡せばスレッド作成の仕方をコントロールできる分、柔軟性があるとも言えると思う。