スレッドを実行するならExecutorフレームワーク。

スポンサードリンク

概要

スレッドを効率的に実行可能なExecutorフレームワークが、
JavaSE1.5以降では利用できる。

古い入門書ではThread.start()が使われていたりするが、
JavaSE1.5以降であれば、Thread.start()ではなくExcecutorを使う。

Exectorフレームワークの中心は、スレッドプール

Thread.start()に比べて、スレッドプールには次のような利点がある。

スレッドの作成と破棄にはオーバーヘッドがかかる。
スレッドを無秩序に作成してしまうと、このオーバーヘッドが馬鹿にならなくなり、
逆に処理が遅くなってしまうこともある。

そこでスレッドプールを利用して、スレッドを再利用する。

スレッドを再利用することによって、
スレッド作成のオーバーヘッドや作りすぎによるメモリ不足に陥るのを防ぐことができる。
オーバーヘッドによる時間やメモリの節約という面では、DBのコネクションプールと同じような発想。

Exctorフレームワークではこのスレッドプールに加えて、
ライフサイクルを管理するメソッドなども提供するのでそれだけでも使う価値がある。

スレッドプール

Executorフレームワークでは、いくつかのスレッドプールの実装が用意されている。
Executorsクラスのファクトリメソッドを利用して、生成する。

おおまかな種類と特徴は次の通り。

ファクトリメソッド 特徴
newFixedThreadPool
newCachedThreadPool
newSingleThreadPool
newScheduledThreadPool

基本的な使い方

スレッドをファクトリメソッドから取得して、実行する。
このとき、shutdown()を忘れないことが重要。

public class SimpleExecutor {
    private static final Logger log = LoggerFactory.getLogger(SimpleExecutor.class);
 
    public void start() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
         
        try {
            executorService.execute(new SimpleRunnnable());
            executorService.execute(new SimpleRunnnable());
        } finally {
            executorService.shutdown();
            executorService.awaitTermination(1, TimeUnit.MINUTES);
        }
    }
 
    static class SimpleRunnnable implements Runnable {
        @Override public void run() {
            log.debug("スレッド終了");
        }
    }
 
    public static void main(String[] args) throws Exception {
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        simpleExecutor.start();
    }
}

いきなりshutdown()することに慣れていないと奇妙に思えるが、これでいい。

ExecutorServiceは、タスクを依頼したスレッドとはまた別のスレッドで実行される。
(実装によっては同じスレッドもありえるが、別スレッドの方が普通。)

よって、shutdown()せずに実行だけしてしまうと、
Executor自体のスレッドを停止できない自体が発生してしまう。

JVMの仕様では実行中のスレッドが全て終了するまでJVMは終われないので、
依頼したタスクが終了してもExecutorのスレッドを停止できずにJVMを自然に終了できなくなる。

よって、shutdown()が重要。

実はシャットダウンに関するメソッドは2つある。
shutdown()とshutdownNow()。

平たく言うと、shutdown()は今依頼してあるタスクが終了したら終了してくださいというぐらい話。
一方、shutdownNow()は直ちに終了せよという意味。

だから、shutdown()を利用している上記の例では
タスクが終了したときに終わるようになっている。

shutdown()というメソッド名から想像すると、
即終了のイメージだが実際はそうではないので要注意。

最後のawaitTermination()は、万が一スレッドが長時間終了できなかった場合用のブロック処理。
これを入れておくことで、指定時間にタイムアウトで終わらせることが可能。

PR