タイマー実行
1.4まではjava.util.Timerを使うところでしたが、5.0からはconcurrentパッケージに導入されたScheduledExecutorServiceを使うべし!とEffective Javaにあります。
ということで、ちょっとお勉強。
ScheduledExecutorServiceの生成
基本的には以下のどちらかを使います。
- Executors#newSingleThreadScheduledExecutor()
- Executors#newScheduledThreadPool(int)
1つ目のは、スケジュールしても実行時に利用するスレッド数は常に1つというシンプルなもの。詳しくは後で。
2つ目のは、スケジュールして実行する際に並行して処理するもの。詳しくは後で。
実行(その前にRunnableとCallable)
Runnableインタフェースは昔からあるインタフェースで、runメソッドを実装する必要があります。
public void run() { }
一方、5.0から増えたCallable
public V call() {
}
Callableを使うと、スレッドでの処理結果を受け取る事ができるようになります!
実行(scheduleメソッド)
ScheduledExecutorServiceで実装されているメソッドは3つあります。
- schedule
- scheduleWithFixedDelay
- scheduleAtFixedRate
まずはscheduleから。これは更に2つあって、第一引数がRunnableかCallableかをとるものです。
Runnableバージョン。
ScheduledFuture<?> future = service.schedule(new Runnable() { public void run() { System.out.println("hello, world!"); } }, 1, TimeUnit.SECONDS);
1秒後にhello, worldを出力します。
次はCallableバージョン。
ScheduledFuture<Integer> future = service.schedule(new Callable<Integer>() { public Integer call() throws Exception { return 5; } }, 1, TimeUnit.SECONDS); int result = future.get(); System.out.println(result);
戻り値を取得する事ができます!
注意点は、これを実行するとプログラムが終了しない点です。
service.shutdown();
こう書くことで、呼び出し側はスケジュールされた処理が全て終わった時点で終了となります。
実行(scheduleWithFixedDelay)
定期実行させる時に使用するメソッドです。
ScheduledFuture<?> future = service.scheduleWithFixedDelay(new Runnable() { public void run() { System.out.println("あ"); } }, 1, 2, TimeUnit.SECONDS);
この場合、1秒後に処理が開始され、処理が終わってから2秒後にまた処理が開始され、、という風に実行します。
次の周期までに処理が終わっていなかった場合は、処理終了を待ってからすぐ実行されます。
止めるには、
service.shutdown();
とするか、次のようにするかのどちらかです。
future.cancel(true);
service.shutdown();
実行(scheduleAtFixedRate)
さきほどのscheduleWithFixedDelayとぱっと見は非常に似ています。
こちらは、再実行のタイミングが異なります。処理終了からどれだけ待つかという意味になります。
ScheduledFuture<?> future = service.scheduleAtFixedRate(new Runnable() { public void run() { System.out.println("あ"); } }, 1, 2, TimeUnit.SECONDS);
スケジュールの同時実行
シングルスレッド編。
同時に処理されるように5件をスケジュール。
ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(); Runnable command = new Runnable() { public void run() { try { Thread.sleep(1 * 1000); System.out.println("Thread:" + Thread.currentThread().getId()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }; service.schedule(command, 1, TimeUnit.MICROSECONDS); service.schedule(command, 1, TimeUnit.MICROSECONDS); service.schedule(command, 1, TimeUnit.MICROSECONDS); service.schedule(command, 1, TimeUnit.MICROSECONDS); service.schedule(command, 1, TimeUnit.MICROSECONDS); service.shutdown();
実行結果としては、ゆっくりと5sかけて、以下の結果が表示されます。
Thread:7 Thread:7 Thread:7 Thread:7 Thread:7
スレッドIDは全て同じ値を指している事が分かります。
つまり、実行は単一スレッドで行っているということですね。
次にスレッドプール編。
ScheduledExecutorServiceの生成部分だけ変えます。
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
実行結果は、1sで以下の結果が表示されます。
Thread:8 Thread:9 Thread:7 Thread:11 Thread:10
すべて別のスレッドIDが振られています。
もう予想が付きますが、宣言部を2に変えて実行します。
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
実行結果はこんな感じ。
Thread:8 Thread:7 Thread:8 Thread:7 Thread:8
スレッドIDは2種類ですね。