Java8対応版Java以前、このような記事を書きました。Utilitiesだけにフォーカスして、全体が分かるように整理してまとめ直しました。UtilitiesのAPIをいくつかのグループに分類し、それぞれのグループの主要な機能を「広く浅く」紹介する、という形式でまとめています。パッケージ単位で分け、それからjava.util.concurrentをさらに機能ごとに分類しています。java.util.concurrentパッケージの「説明」のところに記載されているものが分かりやすいので、それを参考に分けてみました。パッケージロック・フレームワーク内容のほとんどが、APIドキュメントを根気よく読めば分かる情報です。残りはソースコードを読んだり実際に使ってみて得た情報です。今回はJava8対応版なので、サンプルコードではラムダ式やストリームを使っています。実行環境は、Java8である以外は特に指定しません。今回の内容では、プラットフォームに依存するものは扱っていない認識です。APIの動作を実際に動かして確認したいとき、APIドキュメントに記載されているサンプルコードの多くはクラス側の実装しか書いてないので、それを使って動かすコードを自前で書く必要があります。この記事では、簡単に動作をお試しいただけるように、記事中のサンプルコードはそのままmainメソッドに貼りつけてimportを構成すれば実行できるようにしています。コードを簡潔にするために、いくつかユーティリティーメソッドを定義していますので、下記の雛形をお使いください。IDEをお使いでない方は、各コードの先頭にコメントアウトしたimport文を置いていますのでこれをご利用ください。(Executor)ここで言うタスク・フレームワークとは、タスク(Runnable/Callable/FutureTask)、スレッドプール(ThreadPool/ExecutorService)、非同期入出力(Future)、などで構成される、このAPIの中核の機能のことです。基本的なタスク・フレームワークの使い方は、Executorsクラスのファクトリーメソッドを使ってスレッドプールを生成し、Runnable,Callable<V>,FutureTask<V>などの実装クラスのタスクを実行させる、というものです。この場合、スレッドプールはExecutorServiceインターフェイス,ScheduledExecutorServiceインターフェイスを介して操作します。前回の記事ではもう少し詳しく説明していますので、こちらも合わせてご覧ください。今回は、Executors.newScheduledThreadPool(int)を使った例を示します。e.printStackTrace();}threadPool.shutdown();log("end");5秒後にタスクを実行するようにスケジューリングし、さらに3秒後にタスクが完了してFutureの結果が返されます。endこのように、別スレッドで遅延実行したタスクの結果を取得する処理を比較的簡単に書くことができます。ExecutorServiceも同様)を呼び出さないと、非デーモンスレッドが残ってしまいプロセスが終了しないので注意が必要です。Executors.newCachedThreadPool()の場合は例外で、このスレッドプールは、60秒間使用されないスレッドを終了させ、キャッシュから削除するようになっていますので、shutdown()を呼び出さなくても60秒後にアプリケーションが終了します。下記はExecutors.newCachedThreadPool()の説明の引用です。60秒間使用されなかったスレッドは、終了して、キャッシュから削除されます。ドキュメント以下の表は、Executorsクラスのメソッドが実際にどんなクラスのインスタンスを返しているのかを調べたものです。Executors.DelegatedScheduledExecutorService特にこだわりが無ければ、これらのメソッドで生成したスレッドプールを使用します。スレッドプールの初期設定をもっと細かく設定したいのであれば、ThreadPoolExecutorクラスやScheduledThreadPoolExecutorクラス、そして次に紹介するForkJoinPoolクラスのコンストラクターを使って構築することもできます。ThreadPoolExecutorのコンストラクターを直接使うことで、コアプールサイズ、keep-alive時間、ワークキュー、スレッドファクトリー、RejectedExecutionHandler(後述)が設定できます。外部キュー以外は、セッターで設定することもできます。サブクラスであるScheduledThreadPoolExecutorでは、最大プールサイズ、keep-alive時間をコンストラクターで設定することができないので、セッターで設定します。ScheduledThreadPoolExecutorのワークキューは、常に内部クラスのDelayedWorkQueueが使用されます。RejectedExecutionHandlerインターフェイスは、名前の通り、タスク実行がリジェクトされた時のハンドラーです。submitなど)は、キューの容量オーバーなどでリジェクトされることがあります。どのような場面で使用されるかは、下記ページを見ればある程度分かると思います。ドキュメントThreadPoolExecutorのサブクラスにいくつかの実装クラスがあります。Executorsメソッドで生成したスレッドプールのデフォルトのRejectedExecutionHandlerには、ThreadPoolExecutor.AbortPolicyが設定されています。これにより、リジェクトの際にはRejectedExecutionExceptionがスローされます。次は、Fork/Joinフレームワークについてです。Java7では、Fork/Joinフレームワーク(以下Fork/Joinと表記)が追加されました。従来のタスク・フレームワークでは、開始したタスクは1つのスレッドがタスクが終了するまで1つのスレッドで処理するようになっていました。Fork/Joinでは、投入されたタスクだけでなく、処理中のタスク内で分割(fork)された新しいタスクを処理しようとします。en:wikipedia)と呼ばれます。*1Fork/Joinのためのスレッドプールは、ForkJoinPoolクラスを使用します。タスクは、ForkJoinTask<V>もしくはそのサブクラスを使用します。具体的な使用例は、下記の記事などを参照してください。noteForkJoinPoolはスレッドプールで使用するプロセッサー数を制御することができます。使用できるプロセッサーの数は、Runtime.availableProcessors()で取得することができます。true)と同じです。ForkJoinPoolは共通プールのインスタンス(以下、Fork/Join共通プールと表記)があり、ForkJoinPool.commonPool()で取得できます。このプールは静的に構築されます。shutdown()またはshutdownNow()を試行しても、その実行状態には影響しません。このプールおよび実行中の処理は、System.exit(int)プログラムで自動的に終了します。ドキュメントFork/Join共通プールは、Java8からシステムプロパティーで初期設定値を設定することが可能になりました。プロパティーは、ForkJoinPoolクラスの説明に書かれています。Fork/Join自体ではありませんが、Java8では、ご存じStreamのパラレル(並列処理)と、ArraysクラスのparallelSortメソッドを始めとするパラレル処理が追加されています。これらのパラレル実行には、ForkJoinPoolのFork/Join共通プールが使用されています。最後に、Java8で追加されたCompletableFutureについて。CompletableFuture&ltT>はFutureではありますがタスクではありません。それ自体が従来のExecutorファミリーには属さない独自のタスク・フレームワークと言えます。タスクを実行する際に使用されるスレッドプールは(ThreadPool)ExecutorやForkJoinPool*2です。CompletableFutureクラスは、CompletionStageインターフェイスの実装クラスです。CompletableFutureの完了状態には、成功、失敗、キャンセルがあり、成功の場合は処理結果が取得でき、失敗の場合は例外がスローされます。複数のCompletableFutureを合成してひとつの新しいCompletableFutureを生成することもできます。Utilitiesと同時に*3導入されました。キューというデータ構造自体はもちろん、並列処理に限定されるデータ構造では無いのですが、Javaではそれ以前にキューという型がありませんでした。ちなみに、両端キューすなわちデック(Deque)は、さらに遅れてJava6になってから登場します。(java.util.concurrent.BlockingQueue<E>)の実装クラスになります。これは、データを格納する際にキューに空きが無かったり、データを取り出す際にデータが無かった場合に処理をブロックするタイプのキューです。BlockingQueueのほとんどの実装は「スレッドセーフ」なコレクションです。BlockingQueueの使い道といえば、Producer-Consumerパターンが有名です。これは、データのやりとりで必要な同期処理をすべて同期キューに任せることができ、生産者(Producer)と消費者(Consumer)は同期を意識しないで済むというパターンです。下記のサンプルコードは、複数の生産者と複数の消費者でキューを共有する例です。生産者はランダムな数を生産し、消費者はその数(秒)だけスリープする、特に意味のない処理です。interrupted)pool-1が生産者のスレッドプール、pool-2が消費者のスレッドプールです。ログだけでは動きが掴みにくいと思いますが、生産者と消費者がお互いの処理のタイミングを意識することなく動作しているのが分かるでしょうか。この項の内容とは関係ありませんが、ログの最後のところは、スレッドプールはmainスレッドが30秒後に強制的にシャットダウンさせているのでこうなっています。シンクロナイザーシンクロナイザーとは、複数のスレッド間での待ち合わせ(同期)を支援する機能のことです。Java8の時点では、5つのシンクロナイザーの実装があります。それぞれがある特定の同期方法を支援するための機能を持っています。※Java7で追加Semaphoreは、いわゆるカウンティングセマフォーです。許可の数(カウント)を決めておき、スレッドが必要な数の許可を獲得できれば処理を行い、許可は獲得した分だけ利用不可の状態にします。処理が終わったら許可を返却すると、それらの許可は再度利用可能になります。必要な数の許可を獲得できなければ、待機させたり処理をスキップさせたりすることができます。同時にタスクを実行できるスレッド数を制限する場合などに使用します。下記のサンプルコードは、入場制限のシミュレーションです。