読者です 読者をやめる 読者になる 読者になる

【Effective Java】項目68:スレッドよりエグゼキューターとタスクを選ぶ

Java Effective Java

Java 1.5 から、エグゼキューターフレームワークが利用できるようになりました。 このフレームワークを使うと、スレッドで実行するためのワークキューを簡単に作成することができます。

// スレッドで実行するためのエグゼキューターサービスの生成
ExecutorService executorService = Executors.newSingleThreadExecutor();

他のスレッドで処理を実行するには次のように書きます。

// Runnable の実行
executorService.submit(new Runnable() {
        @Override public void run() {
            // 他のスレッドで実行される
            System.out.print("run");
        }
   });

実行中の処理が終わるのを待ち、エグゼキューターを終わらせるコードは次のようになります。

executorService.shutdown();

このようにエグゼキューターフレームワークを使うことで、スレッドで実行する処理をかなり簡単に書くことができるようになりました。

柔軟性

エグゼキューターサービスは従来のスレッドクラスにはなかった次のような機能を持っています。

  • 特定のタスクの完了を待つ
  • タスクの集まりの一部/すべての終了を待つ
  • タスクが完了するごとに結果を取り出す

スレッドプール

キューを実行するスレッドを2本以上持つ『スレッドプール』も簡単に利用可能です。

小さく負荷の低い処理を行う場合には、Executors.newCachedThreadPool() を使うとよいでしょう。

このスレッドプール実装ではタスクの実行が要求されると、新しいスレッドを生成します。 もし、タスクを処理していないスレッドがあれば、そのスレッドは再利用されるます。

ただし、一度にたくさんのタスクが投入されるとその数だけスレッドを生成してしまうため、リソースが枯渇する可能性があります。 一度に大量のタスクが投入されることが想定されるのならば、最大スレッド数が制限された Executors.newFixedThreadPool() を使うべきです。

Thread の利用は避ける

エグゼキューターフレームワークによって自分でワークキューを書く必要性はなくなりました。 それに加えて、直接 Thread クラスを利用する必要もほとんどありません。

従来の Thread クラスのインタフェースには「機能」と「機構」が混在しています。 エグゼキューターフレームワークでは「機能」と「機構」は分離されています。

機能に対応するクラスは「タスク」と呼ばれています。 具体的には Runnable インタフェースと Callable インタフェースが対応します。 Runnable は値を返しませんが、Callable は値を返すことができます。

一方、機構に対応するのがエグゼキューターです。 機構はタスクである Runnable や Callable を実行するためのフレームワークです。

ScheduledThreadPool

エグゼキューターフレームワークは従来の Timer クラスの代わりにもなります。 従来の Timer は実行スレッドが一つしかないため、次のような欠点がありました。

  • 長い時間動作するタスクがある場合にタイマーの精度が悪くなる
  • 唯一のスレッドが例外を投げると Timer 全体が動作しなくなる。

エグゼキューターフレームワークの ScheduledThreadPoolExecutor はこれらの問題を解決しています。

ScheduleThreadPoolExecutor は複数スレッドをサポートしています。 また、タスクからチェックされない例外がスローされた場合にも、実行スレッドは正しく回復します。