Timer ist eine frühe API in JDK. Normalerweise verwenden wir Timer und TimerTask, um Aufgaben mit Verzögerungen und Periodizität zu erledigen, bevor ein NewsschonedThreadpool herauskommt, aber Timer hat einige Fehler. Warum sagen wir das?
Timer erstellt nur einzigartige Threads, um alle Timeraufgaben auszuführen. Wenn die Ausführung einer Timer-Aufgabe zeitaufwändig ist, verursacht dies Probleme mit der Genauigkeit anderer Timertasks. Beispielsweise wird eine Timertask alle 10 Sekunden ausgeführt und ein weiteres Timertask wird alle 40 ms ausgeführt. Die wiederholten Aufgaben werden 4 Mal in Folge aufgerufen, nachdem die nachfolgenden Aufgaben abgeschlossen sind oder 4 Anrufe vollständig "verloren" sind. Ein weiteres Problem mit dem Timer ist, dass TimerTask eine ungeprüfte Ausnahme ausgelöst wird, sondern den Timer -Thread beendet. In diesem Fall antwortet der Timer nicht erneut auf die Ausführung des Threads. Es glaubt fälschlicherweise, dass der gesamte Timer abgesagt wurde. TimerTask, das geplant wurde, aber noch nicht ausgeführt wurde, wird nie wieder ausgeführt, und neue Aufgaben können nicht geplant werden.
Hier habe ich eine kleine Demo gemacht, um das Problem zu reproduzieren. Der Code lautet wie folgt:
Paket com.hjc; import java.util.timer; import Java.util.timertask;/*** Erstellt von Cong am 2018/7/12. */public class timertest {// Timer -Objekt statische Timer Timer = new Timer (); public static void main (String [] args) {// Aufgabe 1, Delay Execution Timer.Schedule (neuer Timertask () {@Override public void run () {System.out.println ("-One Task ---"); try {thread.sleep (1000);} catchedException E) {E.Printstacktra (); ");}}, 500); // Aufgabe 2 hinzufügen, Verzögerungsausführungs-Timer.Schedule (neuer Timertask () {@Override public void run () {for (;;) {System.out.println ("-zwei Task ---"); try {thread.sleep (1000); }}, 1000); }}Wie oben erwähnt, wurde zuerst eine Aufgabe hinzugefügt, um nach 500 ms auszuführen, und dann wurde die zweite Aufgabe hinzugefügt, um nach 1s auszuführen. Was wir erwarten, ist, dass bei der ersten Ausgabe die erste Aufgabe-eine Aufgabe-und auf 1s wartet, die zweite Aufgabe ausgibt-zwei Aufgabe ---
Nach der Ausführung des Codes lautet die Ausgabe jedoch wie folgt:
Beispiel 2,
öffentliche Klassenschild {privater statischer langer Start; public static void main (string [] args) {timerTask task = new timerTask () {public void run () {System.out.println (System.CurrentTimemillis ()-start); try {thread.sleep (3000); } catch (interruptedException e) {e.printstacktrace (); }}}; TimerTask Task1 = new timerTask () {@Override public void run () {System.out.println (System.currentTimemillis ()-start); }}; Timer Timer = new Timer (); start = system.currentTimemillis (); // eine geplante Aufgabe starten, Timer.Schedule ausführen (Aufgabe 1000); // eine geplante Aufgabe starten, Timer.Schedule ausführen (Task1,3000); }}Wir gehen davon aus, dass das obige Programm ausgeführt wird, nachdem die erste Aufgabe nach der zweiten Aufgabe ausgeführt wurde, dh ein 1000 und einen 3000 ausgeben.
Die tatsächlichen Betriebsergebnisse sind wie folgt:
Die tatsächlichen Betriebsergebnisse sind nicht so, wie wir es wünschen. Das Ergebnis der Welt ist, dass die zweite Aufgabe nach 4s ausgegeben wird, dh 4001 sind etwa 4 Sekunden. Wo ist dieser Teil der Zeit gegangen? Diese Zeit war durch den Schlaf unserer ersten Aufgabe besetzt.
Jetzt entfernen wir Thread.sleep () in der ersten Aufgabe; Läuft diese Codezeile richtig? Die Betriebsergebnisse sind wie folgt:
Es ist ersichtlich, dass die erste Aufgabe nach 1s ausgeführt wird und die zweite Aufgabe nach 3S nach Ausführung der ersten Aufgabe ausgeführt wird.
Dies bedeutet, dass Timer nur einen einzigartigen Thread erstellt, um alle Timeraufgaben auszuführen. Wenn die Ausführung einer Timer-Aufgabe zeitaufwändig ist, verursacht dies Probleme mit der Genauigkeit anderer Timertasks.
Timer -Implementierungsprinzip -Analyse
Das Folgende ist eine kurze Einführung in das Prinzip des Timers. Die folgende Abbildung ist eine Einführung in das Prinzipmodell des Timers:
1. TaskQueue ist eine vorrangige Warteschlange, die durch einen ausgewogenen Binärbaumhaufen implementiert wird, und jedes Timer -Objekt verfügt über eine einzigartige Taskqueue -Warteschlange im Inneren. Mit der Zeitplanmethode des Timer -Timer -Timer -Timer -TimerTask -TimerTask -TimerTask -TimerTask -TimerTask -Timer -Times -Times -Timer -Timer -Timer -TimerTask -Aufgabe zur TaskQueue -Warteschlange fügen Sie die TimerTask -Aufgabe hinzu. Beim Aufrufen der Zeitplanmethode wird der Parameter mit langer Verzögerung verwendet, um anzuzeigen, wie lange die Aufgabe verzögert wird, um ausgeführt zu werden.
2. TimerThread ist der Thread, der eine bestimmte Aufgabe ausführt. Es erhält die Aufgabe mit der geringsten Priorität aus der TaskQueue -Warteschlange für die Ausführung. Es ist zu beachten, dass erst nachdem die aktuelle Aufgabe ausgeführt wurde, die nächste Aufgabe von der Warteschlange erhalten wird. Unabhängig davon, ob in der Warteschlange eine festgelegte Verzögerungszeit vorhanden ist, hat ein Timer nur einen TimerThread-Thread. Daher ist ersichtlich, dass die interne Implementierung von Timer ein Einzelverbrauchermodell mit mehreren Produkten ist.
Aus dem Implementierungsmodell können wir wissen, dass Sie, um das obige Problem zu untersuchen, nur die Implementierung von TimerThread betrachten müssen. Der logische Hauptquellencode der Auslaufmethode von TimerThread ist wie folgt:
public void run () {try {mainloop (); } endlich {// Jemand hat diesen Thread getötet, als hätte der Timer synchronisiert (Warteschlange) {newTasksMayBeschemed = false; queue.clear (); // Veraltete Referenzen eliminieren}}} private void mainloop () {while (true) {try {timerTask -Aufgabe; boolescher Aufgabe; // Synchronisierte sperren (Warteschlange) {......} if (taskfired) task.run (); // Task ausführen} catch (InterruptedException e) {}}}Es ist ersichtlich, dass bei einer anderen Ausnahme als InterruptedException während der Aufgabenausführung der einzige Verbraucher -Thread endet, da eine Ausnahme ausgelöst wird und andere Aufgaben, die in der Warteschlange ausgeführt werden sollen, gelöscht werden. Daher ist es am besten, die Try-Catch-Struktur zu verwenden, um die wichtigsten möglichen Ausnahmen in der Run-Methode von Timertask zu fangen und die Ausnahmen nicht außerhalb der Laufmethode zu werfen.
Um Funktionen ähnlich wie Timer zu implementieren, ist es eine bessere Wahl, den Zeitplan von Scheduled threadpoolexecutor zu verwenden. Eine Aufgabe in ScheduledThreadpoolexecutor macht eine Ausnahme, und die anderen Aufgaben sind nicht betroffen.
Das geplante Threadpoolexecutor -Beispiel lautet wie folgt:
/*** Erstellt von Cong am 2018/7/12. */Public Class ScheduledThreadPoolexecutOstest {static afuledThreadpoolexecutor ender threadpoolexecutor = new SchedulledThreadpoolexecutor (1); public static void main (string [] args) {teplanedThreadpoolexecutor.Schedule (new Runnable () {public void run () {System.out.println ("-eine Aufgabe ---"); try {thread.sleep (1000); }, 500, TimeUnit.MicroSeconds); afulledThreadpoolexecutor.Schedule (new Runnable () {public void run () {für (int i = 0; i <5; ++ i) {System.out.println ("-zwei Task ---"); try {thread.sleep (1000);} catch (InterrutedException E) {{E. printace (), {{E. printace (); TimeUnit.Microsekunden); plantedThreadpoolexecutor.shutdown (); }}Die Betriebsergebnisse sind wie folgt:
Der Grund, warum andere Aufgaben der geplanten Threadpoolexecutor nicht von der Aufgabe beeinflusst werden, die Ausnahmen ausführt, liegt darin, dass die Ausnahme die Ausnahme in der afulledFuturetask -Aufgabe in geplanter Threadpoolexecutor abfällt. Es ist jedoch die beste Praxis, Fang, um Aussagen und Druckenprotokolle in der Laufmethode der Thread -Pool -Aufgabe zu fangen.