年も明けた今回は、Concurrency Utilitiesのアップデートについて紹介していきます。Concurrency Utilitiesは、前回までのProject Lambdaとの関係も深く、Project Lambda関連の変更も多くあります。
Project Lambda関連以外の主なアップデートには、次のようなものがあります。
- CompletableFuture
- CountedCompleter
- DoubleAccumulator/DoubleAdder
- LongAccumulator/LongAdder
- StampedLock
- ConcurrentHashMapの変更
CompletableFutureクラスはJava SE 8で新たに導入されたjava.util.concurrent.Futureインタフェースの実装クラスです。このクラスに関しては、この後詳しく説明します。
CountedCompleterクラスも新たに導入されたクラスで、Fork/Join Frameworkのタスクを表すために使用します。
DoubleAccumulatorクラス、DoubleAdderクラス、LongAccumulatorクラス、LongAdderクラスの4種類のクラスは、いずれもjava.util.concurrent.atomicパッケージで定義されています。既存のAtomicDoubleクラス、AtomicLongクラスとは使用目的が異なり、更新頻度が高い場合に使用します。
StampedLockクラスは、クラス名から分かるように、ロックのための新しいクラスです。悲観的ロックをサポートしているなどの特徴があります。
前回、Mapインタフェースについてのアップデートを紹介しましたが、ConcurrentHashMapクラスはMapインタフェースの拡張だけには留まらず、さらに強化されたクラスです。たとえば、ConcurrentHashMapクラスのstaticな内部クラスとしてKeySetViewクラスが導入されています。
今回は、これらのアップデートの中からCompletableFutureクラスを紹介します。
CompletableFuture
java.util.concurrent.CompletableFutureクラスは、非同期に処理する複数のタスクを組み合わせて実行するためのクラスです。
CompletableFutureクラスの説明を行う前に、なぜそのようなクラスが必要になったのかを紹介しておきましょう。
CompletableFuture導入の背景
ここで例として非同期に処理を行うたいa()というメソッドがあったとします。そして、a()メソッドが完了した後、b()メソッドを処理します。b()メソッドが完了したら、c()メソッドを処理させるということを考えましょう。
さて、これをどう書けばいいでしょうか。
まずはダメな例を紹介しましょう。
リスト1 複数のタスクを非同期で実行するダメな例
ExecutorService executor = Executors.newCachedThreadPool();
Future<?> futureA = executor.submit(() -> a());
// a()の完了を待つ
futureA.get();
Future<?> futureB = executor.submit(() -> b());
// b()の完了を待つ
futureB.get();
Future<?> futureC = executor.submit(() -> c());
Futureインタフェースのgetメソッドはタスクが完了するまで返らないため、この書き方だとgetメソッドをコールしているスレッドがブロックされてしまいます。これでは何のための非同期処理なのか分かりません。