Future
Java5で導入されたインターフェース。
将来の、ある地点において利用可能となりうる結果をモデリングするために使用される。
非同期処理をモデリングし、処理完了時にその結果への参照を提供する。
Futureにより、呼び出し元のスレッドが処理結果を待つ代わりに、別の処理を続行することが可能となる。
Java8 In Action
Future使うことで嬉しいこと
- 時間のかかる処理で待たされることがなくなる
- 低レイヤーのスレッドよりも作業しやすくなる
使用方法
Callable
オブジェクト内に、時間のかかる処理をラップし、ExecutorSericeへ渡す。
ExecutorService executor = Executors.newCachedThreadPool();
// タスクをスレッドプールに渡す
Future<Double> future = executor.submit(new Callable<Double>() {
public Double call() { // 別スレッドで処理を実施
return doSomeLongComputation();
}});
doSomethingElse();
Futureのgetメソッドを使って、非同期処理の結果を取得する。
実際の使用では、複数の例外をキャッチする必要がある。
Future.get
Double value = future.get();
Double result = future.get(1, TimeUnit.SECONDS); // タイムアウト時間を指定
Futureを使う非同期処理で考慮すべきこと
####「ラップした処理doSomeLongComputation
がいつまでも結果を返さない場合」
以下のメソッドを使うことで、
非同期処理が完了したかどうかについては確かめることができる
- isDone
- このタスクが完了した場合はtrueを返します。
- isCancelled
- このタスクが正常に完了する前に取り消された場合はtrueを返します。
では、
####「FuturedoSomeLongComputation
の結果を、別のFuturedoSomeMoreLongComputation
が使う場合」
はどうすべきか?
ややこしいポイントは、非同期処理の間で実行順序が存在するところ。
2つ目のfutureを実行するにあたり、
1つ目のfuturedoSomeLongComputation
の結果を能動的に確認する必要がある。
ExecutorService executor = Executors.newCachedThreadPool();
Future<Double> future = executor.submit(new Callable<Double>() {
public Double call() {
return doSomeLongComputation();
}});
// 1つ目のfutureの処理が完了するまで監視
do {
System.out.println("in while");
} while(!future.isDone());
Future<Double> futureSecond = executor.submit(new Callable<Double>() {
public Double call() {
try {
return doSomeLongMoreComputation(future.get());
} catch (InterruptedException | ExecutionException e) {
return 0.0;
}
}});
while文を使わずとも、非同期処理間で順序を持たすような処理を書くことはできる。
long start = System.nanoTime();
ExecutorService executor = Executors.newCachedThreadPool();
System.out.println(String.format("before future elapsed time: %d msecs", (System.nanoTime()-start)/1_000_000));
// 1つ目のfuture
Future<Double> future = executor.submit(new Callable<Double>() {
public Double call() {
return doSomeLongComputation();
}});
System.out.println(String.format("after future elapsed time: %d msecs", (System.nanoTime()-start)/1_000_000));
//2つ目のfuture
Future<Double> futureSecond = executor.submit(new Callable<Double>() {
public Double call() {
try {
System.out.println(String.format("before future.get elapsed time: %d msecs", (System.nanoTime()-start)/1_000_000));
double value = future.get(); // 1つ目のfutureをcall
System.out.println(String.format("after future.get elapsed time: %d msecs", (System.nanoTime()-start)/1_000_000));
// 1つ目のfutureの処理結果を使用
return doSomeLongMoreComputation(value);
} catch (InterruptedException | ExecutionException e) {
return 0.0;
}
}});
try {
System.out.println(String.format("before futureSecond.get elapsed time: %d msecs", (System.nanoTime()-start)/1_000_000));
// ここで2つ目のfutureをcall
double d = futureSecond.get();
System.out.println(String.format("after futureSecond.get elapsed time: %d msecs", (System.nanoTime()-start)/1_000_000));
} catch (InterruptedException | ExecutionException e) {
System.out.println(e);
}
結果
入れ子となっているfutureの処理の完了するまで、2つ目のfutureの処理はブロックされる(∵ 2つ目のfutureは、1つ目のfutureの処理結果を使用するため)
before future elapsed time: 3 msecs
doSomeLongComputation called
after future elapsed time: 25 msecs
before futureSecond.get elapsed time: 26 msecs <-2つ目のfuture開始
before future.get elapsed time: 26 msecs <- 入れ子となっている1つ目ののfuture開始
doSomeLongComputation: 10
after future.get elapsed time: 10027 msecs <- 入れ子となっている1つ目ののfuture終了
doSomeLongMoreComputation called
doSomeLongMoreComputation: 11
after futureSecond.get elapsed time: 20029 msecs <-2つ目のfuture終了
結局実現したいことは...
- 2つの非同期処理をまとめる
- Futureの塊によって実行された全ての処理の完了を待つ
- Futureの塊で、最も早く実行されたタスクのみの完了を待つ
- Futureの完了をプログラムで実行
- Futureの完了に対し対応する
- 処理完了の際に通知
- 処理結果を待つのではなく、Futureの結果を使って処理を実行
これらを、
Java8の特徴を用いて、
宣言的な方法で実行したい場合に、CompletableFuture
を使用する。