Advanced Java Services | 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.
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.
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()); } }
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(); }
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.
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
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.
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
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);
In diesem Fall kann die Periode nicht eingehalten werden. Der neue Durchlauf kann erst beginnen, wenn der alte zu Ende ist.