マルチスレッドプログラミングのパターンの一つに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を渡せばスレッド作成の仕方をコントロールできる分、柔軟性があるとも言えると思う。