タイマー実行

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インタフェースは、callメソッドを実装する必要があります。

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種類ですね。