S-JIS[2008-07-29/2008-07-30] 変更履歴

Javaタイマークラス

Javajava.util.Timerは、処理を一定時間間隔で動かしたり、一定時間後に動かしたりする為のクラス。
Timerから、自分で指定したTimerTask(の具象クラス)が呼ばれる。
このタイマーの実行にはThreadが使用されている(マルチスレッドで動作する)。


一定時間後に一度だけ実行する例

	public void timer_delay() throws InterruptedException {
		TimerTask task = new SampleTask();
		Timer timer = new Timer("遅延タイマー");

		System.out.println("main start:" + new Date());
		timer.schedule(task, TimeUnit.SECONDS.toMillis(10)); //10秒後に実行

		TimeUnit.SECONDS.sleep(30); //30秒間待つ
		timer.cancel();
		System.out.println("main end  :" + new Date());
	}
class SampleTask extends TimerTask {

	/** このメソッドがTimerから呼ばれる */
	@Override
	public void run() {
		System.out.println("タスク実行:" + new Date());
	}
}

実行例:

main start:Tue Jul 29 01:58:07 JST 2008
タスク実行:Tue Jul 29 01:58:27 JST 2008
main end  :Tue Jul 29 01:58:37 JST 2008

タイマーインスタンスを作りscheduleメソッド実行すべきタスクインスタンスを渡すと、指定した時間の経過後にタスクのrun()メソッドが呼ばれる。
引数がStringのTimerのコンストラクターは、JDK1.5で新設されたもの。そのタイマーの名前が付けられる)

ちなみにこの場合のschedule()の第2引数には待機時間(delay)をミリ秒で指定するので、10秒待つなら10*1000を渡せばいい。
のだが、せっかくなので、JDK1.5で導入されたTimeUnitを使ってみた(笑)

このメインスレッドでは、30秒待った後にタイマーをキャンセルしている。
試しにこのtimer.cancel()をコメントアウトして実行してみると分かるが、明示的にキャンセルしないと、プログラムが終了しない!
TimerのJavadocには「タスクが全て終了してタイマーインスタンスがどこからも参照されなくなったらGCによって終了する」とあるが、「ただし、限りなく長い時間がかかる場合がある」だって!


TimerはMTセーフなので、タスクが終わったら自前でキャンセルしちゃう方がすっきりするかも。

	private Timer timer;

	public void timer_delay() throws InterruptedException {
		TimerTask task = new TimerTask() {
			@Override
			public void run() {
				System.out.println("タスク実行:" + new Date());
				timer.cancel(); //自分が属しているタイマーをキャンセル(終了)する
			}
		};

		timer = new Timer("遅延タイマー");

		System.out.println("main start:" + new Date());
		timer.schedule(task, TimeUnit.SECONDS.toMillis(10)); //10秒後に実行

		TimeUnit.SECONDS.sleep(30); //30秒間待つ
		System.out.println("main end  :" + new Date());
	}

なお、TimerTask自身にもcancel()メソッドはあるのだが、タイマーを終了させるわけではない。
TimerTask#cancel()の使い道


指定時刻に一度だけ起動する例

	public void timer_date() extends InterruptedException {
		Calendar cal = Calendar.getInstance();
		cal.set(2008, 7 - 1, 29, 2, 30, 20);

		TimerTask task = new DateTask();
		Timer timer = new Timer("日付実験タイマー");

		System.out.println("main start:" + new Date());
		timer.schedule(task, cal.getTime());

		TimeUnit.SECONDS.sleep(10);
		timer.cancel();
		System.out.println("main end  :" + new Date());
	}
class DateTask extends TimerTask {

	@Override
	public void run() {
		System.out.println("タスク実行:" + new Date());
		System.out.println("実行予定 :" + new Date(super.scheduledExecutionTime()));
	}
}

実行例(通常):

main start:Tue Jul 29 02:30:15 JST 2008
タスク実行:Tue Jul 29 02:30:20 JST 2008
実行予定 :Tue Jul 29 02:30:20 JST 2008
main end  :Tue Jul 29 02:30:25 JST 2008

実行例(時刻がとっくに過ぎている場合):

main start:Tue Jul 29 02:33:15 JST 2008
タスク実行:Tue Jul 29 02:33:15 JST 2008
実行予定 :Tue Jul 29 02:30:20 JST 2008
main end  :Tue Jul 29 02:33:25 JST 2008

schedule()の第2引数に(待機時間でなく)日付時刻を渡すと、その日時に実行される。

scheduledExecutionTime()は実行予定日時を(long型で)返してくれる。
例えばマシンがビジー状態だったら予定時刻を過ぎてからrun()が呼ばれる可能性があるので、このメソッドを使って時刻を確認できる。


繰り返し実行する例

shcedule()の第3引数に時間(実行間隔:period)を指定すると、その間隔で繰り返しrun()が呼ばれる。

	public void timer_period() throws InterruptedException {
		TimerTask task = new SampleTask();
		Timer timer = new Timer("時間間隔タイマー");

		System.out.println("main start:" + new Date());
		timer.schedule(task, 0, 5 * 1000);

		TimeUnit.SECONDS.sleep(30);
		timer.cancel();
		System.out.println("main end  :" + new Date());
	}

実行例:

main start:Tue Jul 29 03:00:29 JST 2008
タスク実行:Tue Jul 29 03:00:29 JST 2008
タスク実行:Tue Jul 29 03:00:34 JST 2008
タスク実行:Tue Jul 29 03:00:39 JST 2008
タスク実行:Tue Jul 29 03:00:44 JST 2008
タスク実行:Tue Jul 29 03:00:49 JST 2008
タスク実行:Tue Jul 29 03:00:54 JST 2008
タスク実行:Tue Jul 29 03:00:59 JST 2008
main end  :Tue Jul 29 03:00:59 JST 2008

schedule()の他にscheduleAtFixedRate()というメソッドもある。
schedule()は、実行時間が遅延しても 次に呼ばれるまで指定時間必ず待つ。(固定遅延の実行)
scheduleAtFixedRate()は、遅延すると 次に呼ばれるのが早くなる(連続して実行される)。(固定頻度で実行)

なお、TimerTask#run()内でTimerTask#cancel()を呼び出すと、それ以上そのタスク(run()メソッド)は呼ばれなくなる。
(TimerTask#cancel()を呼び出してもTimer自身がキャンセルされるわけではないので注意)


複数タスクの実行

Timerは、複数のタスクを指定することが出来る。

	public void timers() throws InterruptedException {
		TimerTask task1 = new NamedTask("タスクA");
		TimerTask task2 = new NamedTask("タスクB");
		Timer timer = new Timer("複数タスクタイマー");

		System.out.println("main start:" + new Date());
		timer.schedule(task1, 0, 2 * 1000);
		timer.schedule(task2, 0, 3 * 1000);

		TimeUnit.SECONDS.sleep(10);
		timer.cancel();
		System.out.println("main end  :" + new Date());
	}
class NamedTask extends TimerTask {

	private String name;

	public NamedTask(String name) {
		this.name = name;
	}

	@Override
	public void run() {
		System.out.printf("%-7s:%s%n", name, new Date());
	}
}

実行例:

main start:Tue Jul 29 03:18:00 JST 2008
タスクA   :Tue Jul 29 03:18:00 JST 2008
タスクB   :Tue Jul 29 03:18:00 JST 2008
タスクA   :Tue Jul 29 03:18:02 JST 2008
タスクB   :Tue Jul 29 03:18:03 JST 2008
タスクA   :Tue Jul 29 03:18:04 JST 2008
タスクA   :Tue Jul 29 03:18:06 JST 2008
タスクB   :Tue Jul 29 03:18:06 JST 2008
タスクA   :Tue Jul 29 03:18:08 JST 2008
タスクB   :Tue Jul 29 03:18:09 JST 2008
main end  :Tue Jul 29 03:18:10 JST 2008

ここでは同じNamedTaskというクラスのインスタンスを複数作って一つのTimerインスタンスでスケジューリングしたが、TimerTaskのサブクラスであれば、どんな種類でもスケジュール登録できる。
キャンセルの都合を考えてTimerインスタンスを用意するのがいいかな。


スレッドはタイマー毎に作成される。[2008-07-30]
すなわち、一つのタイマー(Timer)に複数のタスク(TimerTask)を登録しても、それぞれのタスクは同一スレッドで実行される。
別のタイマーインスタンス(Timer)を作ると、それは別スレッドとなる。


スレッドへ戻る / Java目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま