Javaのjava.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)を作ると、それは別スレッドとなる。