Advanced   Java   Services Timer und TimerTask Back Next Up Home


Timer und TimerTask

Die von Josh Bloch entworfene Klasse Timer wurde in 1.3 eingeführt und ermöglicht zusammen mit der Klasse TimerTask das Abarbeiten von Threads vom Typ TimerTask nach einer gewissen Zeit zusammen mit optionalen periodischen Wiederholungen. Mit den verschiedenen von Timer zur Verfügung gestellten Konstruktoren und schedule()-Methoden kann man solche Aufgaben realisieren. Die zu erledigende Arbeit wird in einer Unterklasse von TimerTask realisiert. Timer kann nur mit Objekten vom Typ TimerTask zusammenarbeiten und ein Timer kann nur eine TimerTask abarbeiten.


Timer legt selbst einen Thread an

Jeder Konstruktor von Timer legt sofort einen eigenen Thread an und startet diesen. Über den Konstruktor kann man angeben, ob dieser Thread als Dämon angelegt werden soll, ansonsten ist der erzeugte Thread kein Dämon und kann daher auch nicht von main beendet werden. Zum Beenden steht dann nur die Methode cancel() von Timer selbst zur Verfügung.

Der von Timer angelegte interne Thread ist ein Objekt der Klasse TimerThread, dem ein Objekt der Klasse TaskQueue übergeben wird. TimerThread und TaskQueue sind packageprivate Klassen in java.util. Die TaskQueue arbeitet intern mit einem Array von TimerTasks.

private TimerTask[] queue = new TimerTask[128];

Einige Beispiele sollen das Arbeiten mit diesen Klassen verdeutlichen.


Singleshot

Um eine bestimmte Uhrzeit, oder nach Ablauf einer Zeitspanne soll eine Aufgabe einmal ausgeführt werden. Legt man den Timer als Dämon an, kann es passieren, daß main zu Ende ist bevor der Timer den Task strten soll. In diesem Fall beendet main den Timer und der SingleShot wird nicht ausgeführt. Ist der Timer kein Dämon, so wird der SingleShot ausgeführt, aber der Timer beendigt sich nicht selbst.

Im ersten Beispiel wird nach einer Sekunde die Uhrzeit ausgegeben. Man kann main noch einige Sekunden weiterlaufen lassen, um zu sehen daß der Timer erst durch main beendet wird, wenn man ihn nicht als Dämon anlegt bzw. nicht beendet wird wenn er kein Dämon ist.

Timer timer = new Timer(true); // true = daemon !
timer.schedule(new MyTask1(), 1000);
// Der Timer beendet sich nach dem Singleshot nicht sofort
Thread.sleep(4000);

Die (sehr einfache) Unterklasse von TimerTask

class MyTask1 extends TimerTask
{
   @Override
   public void run()
   {
      System.out.println(new Date());
   }
}

Singleshot (Timer wird durch den Task beendet)

Will man den Timer nach dem SinglShot beenden, so übergibt man ihn der Unterklasse von TimerTask und ruft dann cancel(). Der TimerTask sieht dann wie folgt aus.

class MyTask2 extends TimerTask
{
   Timer timer=null;

   public MyTask2(Timer timer)
   {
      this.timer = timer;
   }

   @Override
   public void run()
   {
      System.out.println(new Date());
      if(timer!=null) timer.cancel();  // beendet den Timer
   }
}

In main kann man das sehen in dem man sich die Anzahl der laufenden Threads ausgeben läßt.

Timer timer = new Timer(true); // true = daemon
timer.schedule(new MyTask2(timer), 1000);
try
{
   Thread.sleep(2000);
   System.out.println("threads running: " + Thread.currentThread().activeCount());
   Thread.sleep(2000);
}
catch(InterruptedException ex)
{
   ex.printStackTrace();
}

Periodischer Task (main cancelt den Timer)

Nach einer Sekunde soll alle 100 Millisekunden die Uhrzeit im Millisekundenformat ausgegeben werden. Der Task ist simpel.

class MyTask3 extends TimerTask
{
   public MyTask3()
   {
   }

   @Override
   public void run()
   {
      System.out.println(new Date().getTime());
   }
}

main cancelt den Timer nach zwei Sekunden.

Timer timer = new Timer();
// startet nach einer Sekunde
// negativer delay führt zu IllegalArgumentException
// ebenso eine Periode <=0
timer.schedule(new MyTask3(), 1000, 100);

try
{
   System.out.println("main starts sleeping " + new Date());
   Thread.sleep(2000);
}
catch(InterruptedException ex)
{
   ex.printStackTrace();
}
System.out.println("main cancels task");
timer.cancel();

Der Output

main starts sleeping Tue Nov 20 12:25:57 CET 2012
1353410758578
1353410758687
1353410758796
1353410758906
1353410759015
1353410759125
1353410759234
1353410759343
1353410759453
1353410759562
main cancels task

Man erkennt, daß es relativ große Abweichungen gibt.


Periodischer Task (verbesserte Genauigkeit)

Mit der Methode scheduleAtFixedRate() läßt sich die Genauigkeit deutlich verbessern. Die Methode versucht Verspätungen aufzuholen. es ist nur eine Zeile zu ändern.

timer.scheduleAtFixedRate(new MyTask3(), 1000, 100);

Output

main starts sleeping Tue Nov 20 12:30:03 CET 2012
1353411004421
1353411004531
1353411004625
1353411004734
1353411004828
1353411004921
1353411005031
1353411005125
1353411005234
1353411005328
1353411005421
main cancels task

Beenden einer Periode

Timer hat keine schedule()-Methode, bei der man angeben kann, wie oft ein Task wiederholt werden soll oder wann er beendet werden soll. Falls man den Timer nicht als Dämon anlegt wird ein periodischer Task "nie" beendet. Falls man ihn als Dämon anlegt, wird er von main beendet. Diese Einschränkung kann man umgehen, wenn man den TimerTask geschickter codiert. Timer realisiert die Wiederholungen durch erneute Aufrufe von run(), legt aber dabei nur ein einziges Objekt von TimerTask an. Dieses Verhalten kann man ausnützen um eine Periode zu beenden. Die folgenden Beispiele zeigen das.


Periodischen Task nach einer Anzahl von Wiederholungen beenden

Mit einem statischen Zähler kann man feststellen, wie oft run() aufgerufen wurde. Das kann man verwenden um eine Periode nach einer gewissen Anzahl von Wiederholungen zu beenden.

Ein TimerTask der den Timer beendet

class MyTask5 extends TimerTask
{
   private static int count = 0;
   private int howOften;

   public MyTask5(int howOften)
   {
      if( howOften < 1) throw new IllegalArgumentException();
      this.howOften = howOften;
   }

   @Override
   public void run()
   {
      count++;
      System.out.println(new Date().getTime());
      if (count >= howOften) this.cancel();
   }
}

Verwendung

Timer timer = new Timer();
MyTask5 mt5 = new MyTask5(7);
timer.schedule(mt5, 1000, 100);

Output

1353411424375
1353411424484
1353411424593
1353411424703
1353411424812
1353411424921
1353411425031


Periodischen Task um eine bestimmte Uhrzeit beenden

Diese Variante ist fast einfacher zu verwirklichen.

class MyTask6 extends TimerTask
{
   private static long now;
   private static long later;

   public MyTask6(Date lastTime)
   {
      now = new Date().getTime();
      later = lastTime.getTime();
   }

   @Override
   public void run()
   {
      now = new Date().getTime();
      if( now >= later)
      {
         this.cancel();
      }
      System.out.println(new Date().getTime());
   }

Verwendung

Timer timer = new Timer();
long now = new Date().getTime();  // milliseconds
long later = now + 4000;
MyTask6 mt6 = new MyTask6(new Date(later));
timer.schedule(mt6, new Date(now + 1000), 500);

Task dauert länger als die Periode

In diesem Fall kann die Periode nicht eingehalten werden. Der neue Durchlauf kann erst beginnen, wenn der alte zu Ende ist.

Valid XHTML 1.0 Strict top Back Next Up Home