Timer est une première API dans JDK. Nous utilisons généralement la minuterie et la chronologie pour effectuer des tâches avec des retards et une périodicité avant que NewscheduleDThreadPool ne sache, mais Timer a des défauts. Pourquoi le dis-nous?
La minuterie ne crée que des fils uniques pour effectuer toutes les tâches de la minuterie. Si l'exécution d'une tâche de temporisation prend du temps, cela entraînera des problèmes avec la précision des autresasses de time. Par exemple, une tricotasure est exécutée toutes les 10 secondes et une autre Timemery est exécutée toutes les 40 ms. Les tâches répétées seront appelées 4 fois de suite une fois les tâches suivantes terminées, ou 4 appels sont complètement "perdus". Un autre problème avec la minuterie est que si Timertask lance une exception non contrôlée, elle mettra fin au fil de minuterie. Dans ce cas, la minuterie ne répondra plus à l'exécution du thread; Il croit à tort que la minuterie entière a été annulée. Timertask, qui a été prévu mais n'a pas encore été exécuté, ne sera plus jamais exécutée et de nouvelles tâches ne peuvent pas être planifiées.
Ici, j'ai fait une petite démo pour reproduire le problème, le code est le suivant:
package com.hjc; import java.util.timer; import java.util.timertask; / ** * créé par Cong le 2018/7/12. * / public class TimerTest {// Create Timer Object Static Timer Timer = new Timer (); public static void main (String [] args) {// ajouter la tâche 1, retard d'exécution Timer.Schedule (new Timemerk () {@Override public void run () {System.out.println ("- une tâche ---"); try {thread.sleep (1000);} Catch (InterruptedException e) {E.PrintStackTrace ();} ");}}, 500); // Ajouter la tâche 2, Delay Exécution Timer.Schedule (new Timertask () {@Override public void run () {for (;;) {System.out.println ("- deux tâches ---"); try {Thread.Sleep (1000);} Catch (InterruptedException e) {// TODO Auto-généré un bloc de capture E.PrinttackTrace ();); }, 1000); }}Comme mentionné ci-dessus, une tâche a d'abord été ajoutée pour exécuter après 500 ms, puis la deuxième tâche a été ajoutée pour exécuter après 1. Ce que nous attendons, c'est que lorsque la première tâche sortira --- une tâche --- et attend 1s, la deuxième tâche sortira - deux tâches ---,
Cependant, après avoir exécuté le code, la sortie est la suivante:
Exemple 2,
Classe publique Shedule {STATIC Long Start privé; public static void main (String [] args) {TIMERTASK Task = new Timemersask () {public void run () {System.out.println (System.CurrentTimemillis () - start); essayez {thread.sleep (3000); } catch (InterruptedException e) {e.printStackTrace (); }}}; TIMERTASK TASK1 = NOUVEAU TIMERTASK () {@Override public void run () {System.out.println (System.CurrentTimeMillis () - START); }}; Timer Timer = new Timer (); start = System.currentTimemillis (); // Démarrez une tâche planifiée, exécutez Timer.Schedule (tâche, 1000); // Démarrez une tâche planifiée, exécutez Timer.Schedule (tâche1,3000); }}Nous nous attendons à ce que le programme ci-dessus soit exécuté après l'exécution de la première tâche après que la deuxième tâche soit 3S, c'est-à-dire une sortie 1000 et un 3000.
Les résultats de l'opération réels sont les suivants:
Les résultats de l'opération réels ne sont pas comme nous le souhaitons. Le résultat du monde est que la deuxième tâche est sortie après 4S, c'est-à-dire 4001 est d'environ 4 secondes. Où est passée cette partie du temps? Ce temps était occupé par le sommeil de notre première tâche.
Maintenant, nous supprimons Thread.Sleep () dans la première tâche; Cette ligne de code fonctionne-t-elle correctement? Les résultats de l'opération sont les suivants:
On peut voir que la première tâche est exécutée après 1 et la deuxième tâche est exécutée après 3s après l'exécution de la première tâche.
Cela signifie que la minuterie ne crée qu'un fil unique pour effectuer toutes les tâches de minuterie. Si l'exécution d'une tâche de temporisation prend du temps, cela entraînera des problèmes avec la précision des autresasses de time.
Analyse des principes de mise en œuvre du minuteur
Ce qui suit est une brève introduction au principe de la minuterie. Le chiffre suivant est une introduction au modèle principal de la minuterie:
1.Taskqueue est une file d'attente prioritaire implémentée par un tas d'arbre binaire équilibré, et chaque objet de minuterie a une file d'attente de taskqueue unique à l'intérieur. La méthode de planification de la minuterie d'appel de thread utilisateur consiste à ajouter la tâche Timertask à la file d'attente TaskQueue. Lors de l'appel de la méthode de planification, le paramètre de retard long est utilisé pour indiquer la durée de la tâche pour être exécutée.
2. TimerThread est le fil qui effectue une tâche spécifique. Il obtient la tâche avec la moindre priorité de la file d'attente TaskQueue pour l'exécution. Il convient de noter que ce n'est que après l'exécution de la tâche actuelle, la prochaine tâche sera obtenue à partir de la file d'attente. Qu'il y ait un temps de retard défini dans la file d'attente, une minuterie n'a qu'un seul thread TirmerTread, il peut donc être constaté que la mise en œuvre interne de la minuterie est un modèle monoconsumerie multi-producteur.
D'après le modèle de mise en œuvre, nous pouvons savoir que pour explorer le problème ci-dessus, vous n'avez qu'à examiner la mise en œuvre de TimerThread. Le principal code source logique de la méthode d'exécution de TimerThread est le suivant:
public void run () {try {mainloop (); } Enfin {// quelqu'un a tué ce fil, agissant comme si Timer avait annulé Synchronized (file d'attente) {NewTasksMayBescheduled = false; queue.clear (); // éliminer les références obsolètes}}} private void mainloop () {while (true) {try {TIMERTASK TASK; Boolean Taskfired; // Lock Synchronized (file d'attente) {......} if (taskfired) task.run (); // exécuter la tâche} catch (InterruptedException e) {}}}On peut voir que lorsqu'une exception autre que InterruptedException est lancée lors de l'exécution des tâches, le seul fil de consommation se terminera en raison de la lancement d'une exception, et d'autres tâches à exécuter dans la file d'attente seront effacées. Par conséquent, il est préférable d'utiliser la structure des couples d'essai pour capter les principales exceptions possibles dans la méthode d'exécution de Timertask, et ne lancez pas les exceptions en dehors de la méthode d'exécution.
En fait, pour implémenter des fonctions similaires à Timer, il s'agit d'un meilleur choix d'utiliser le programme de ScheduledThreadPoolExecutor. Une tâche dans ScheduledThreadPoolExecutor lance une exception et les autres tâches ne sont pas affectées.
L'exemple ScheduledThreadPoolExecutor est le suivant:
/ ** * Créé par Cong le 2018/7/12. * / classe publique ScheduledThreadPoolExECUTOrest {static scheduledThreadPoolExecutor scheduledThreadPoolExecutor = new scheduledThreadPoolExecutor (1); public static void main (String [] args) {scheduledTheredpoolExecutor.Schedule (new Runnable () {public void run () {System.out.println ("- une tâche ---"); try {thread.sleep (1000);} catch (interruptedException e) {e.printStackTrace ();} throw runImException (");") }, 500, timeunit.microsecondes); ScheduledThreadPoolExecutor.Schedule (new Runnable () {public void run () {for (int i = 0; i <5; ++ i) {System.out.println ("- deux tâches ---"); try {thread.sleep (1000);} Catch (interruptedException e) {e.printStackTrace ();}}}} TimeUnit.microsecondes); ScheduledThreadPoolExecutor.shutdown (); }}Les résultats de l'opération sont les suivants:
La raison pour laquelle d'autres tâches de ScheduledThreadPoolExecutor ne sont pas affectées par la tâche qui lève des exceptions est que la catch laisse tomber l'exception de la tâche planifiéefutureTask dans ScheduledThreadPoolExecutor, mais il est de la meilleure pratique d'utiliser des journaux de capture pour capturer et des journaux d'impression dans la méthode d'exécution de la tâche du pool de threads.