2012年12月14日金曜日

ExecutorService の使い方

ここで説明するのは、おそらくもっとも安全にマルチスレッドプログラムを書く方法です。

さらに同様の方法で簡単に拡張することで、複数のスレッドを効率よく使うスレッドプール (Thread Pool) を利用できますので、 ぜひ覚えておきたい方法です。

その方法とは、 ExecutorService を利用することです。

ExecutorService では、Java のマルチスレッド・プログラミングの基本 でみたように Thread オブジェクトをそのまま生では使いません。 プログラムの処理を小さなタスクに分割して、そのタスクを Executor によって実行すると考えます。

タスクの状態

Executor によって処理されるタスクの状態は次の四つです。

ExecutorService - タスクの状態

  1. created (作成された)
    サブミットされていない状態のタスクは created ステートです。
  2. submitted (サブミットされた)
    submit または execute メソッドでタスクをサブミットします。サブミットされても処理が開始しない場合は、通常ブロッキングキューに入ります。 FIFO で処理されます。

    submit された以降、メモリが割り当てられない等何らかの理由で処理がアボートする場合は、 RejectedExecutionException が発生します。

  3. started (処理が開始した)
  4. complted (完了した)

この状態は不可逆で、 created から completed へと順番に変わります。

スレッドプールを容易に使える ExecutorService

さて、スレッドプール (Thread Pool) について簡単に説明します。

スレッドプール

スレッドプールは通常 Web サーバーやデータベースサーバーなど、複数のタスクを同時に素早く処理しなければならない状況で利用されます。

複数のスレッドをあらかじめ作成して待機させておき、タスクが来たら待っているスレッドにタスクを割り当てて処理を開始させる仕組みをスレッドプールと言います。処理すべきタスクが到着してからモタモタとスレッドを起動するのではなく、スレッドをあらかじめ起動しておき、タスクが割り当てられたらすぐに処理を開始出来るようにする、ということです。

ゼロからこうした仕組みを実装するとなると、少々面倒くさいのですが、 Java では ExecutorService というものがあり、スレッドプールをあなたのプログラムで簡単に利用できます。

ExecutorService のコードサンプル

では、実際にスレッドプールを用いてタスクの処理を行う簡単な例を示します。

package com.keicode.java.test;    import java.util.concurrent.ExecutorService;  import java.util.concurrent.Executors;    public class ThreadTest4 {      public static void main(String[] args) {            System.out.println("Entering main");            ExecutorService exec = Executors.newFixedThreadPool(3);            try {        for(int i=0;i<10; i++){          exec.execute(new Runnable(){            public void run() {              try {                Thread.sleep(1 * 1000);                System.out.println("Hello! - "                   + Thread.currentThread().getId());              } catch (InterruptedException e) {                e.printStackTrace();              }            }          });        }                System.out.println("Sleeping..."           + Thread.currentThread().getId());        Thread.sleep(15 * 1000);              } catch(InterruptedException e) {        e.printStackTrace();      }        System.out.println("Exit main");          }    }  

実行結果は次のようになりました。

Entering main  Sleeping...1  Hello! - 8  Hello! - 10  Hello! - 9  Hello! - 10  Hello! - 8  Hello! - 9  Hello! - 10  Hello! - 9  Hello! - 8  Hello! - 10  Exit main  

ここではスレッドプール内に 3 つのスレッドを作成して、そこに 10 個のタスクを投げています。それぞれのタスクは、1 秒待ってから Hello! というメッセージとスレッド ID を出力します。

出力結果から、 ID が 8, 9, 10 という 3 つのスレッドがスレッドプール内で実行されたことがわかりますね。

java.util.concurrent.Executors.newFixedThreadPool というメソッドで、固定数のスレッドを持つスレッドプールを利用する ExecutorService を取得します。

ExecutorService の execute メソッドを呼ぶことで、タスクは submitted 状態になります。

ここでは一度に 10 個のタスクの実行 (execute) を要求していますが、スレッドが 3 個起動されています。このためまず 3 個のタスクが started になり、 残りの 7 個は submitted ステートのままキューの中で待ちます。

開始したタスクが終了したら、キューの中のタスクがスレッドに割り当てられて started 状態になります。

0 件のコメント:

コメントを投稿