El temporizador es una API temprana en JDK. Por lo general, usamos Timer y TimerTask para realizar tareas con demoras y periodicidad antes de que salga NewsCheduledThreadPool, pero el temporizador tiene algunos defectos. ¿Por qué lo decimos?
El temporizador crea solo hilos únicos para realizar todas las tareas del temporizador. Si la ejecución de una tarea de temporizador requiere mucho tiempo, causará problemas con la precisión de otras tareas de tiempo. Por ejemplo, una TimerTask se ejecuta cada 10 segundos, y otro TimerTask se ejecuta cada 40 ms. Las tareas repetidas se llamarán 4 veces seguidas después de completar las tareas posteriores, o 4 llamadas están completamente "perdidas". Otro problema con el temporizador es que si TimeTask lanza una excepción sin control, terminará el hilo del temporizador. En este caso, el temporizador no responderá a la ejecución del hilo nuevamente; Cree erróneamente que todo el temporizador ha sido cancelado. TimeTkask, que ha sido programado pero que aún no se ha ejecutado, nunca volverá a ejecutarse, y no se pueden programar nuevas tareas.
Aquí hice una pequeña demostración para reproducir el problema, el código es el siguiente:
paquete com.hjc; import java.util.timer; import java.util.timerTask;/*** creado por Cong el 2018/7/12. */public class TimeTest {// Crear temporizador de objeto Temporizador estático de temporizador = new Timer (); public static void main (string [] args) {// Agregar Tarea 1, Delay Execution Timer.schedule (new TimeTk () {@Override public void run () {System.out.println ("-una tarea ---"); intente {horthif.sleep (1000);} capt (interruptedException e) {e.preintstacktRace ();} shunteeMeETT ("ERRATE (" ERRATE ("ERROR (" ERROR ("ERROR (" ERROR ("ERRATO (" ERROR ("ERROR (" ERROR ("ERROR) ");}}, 500); // Agregar tarea 2, la ejecución de demora timer.schedule (new TimerTask () {@Override public void run () {for (;;) {system.out.println ("-dos tareas ---"); try {thread.seper (1000);} capt (interruptedException e) {// todo au }}, 1000); }}Como se mencionó anteriormente, se agregó primero una tarea para ejecutar después de 500 ms, y luego se agregó la segunda tarea para ejecutar después de 1s. Lo que esperamos es que cuando la primera tarea salga, una tarea, y espera a 1s, la segunda tarea generará-dos tareas ---,,
Sin embargo, después de ejecutar el código, la salida es la siguiente:
Ejemplo 2,
Shedule de clase pública {comienzo de larga duración privada; public static void main (string [] args) {timeTask task = new TimeTk () {public void run () {System.out.println (System.CurrentTimemillis ()-inicio); intente {Thread.sleep (3000); } catch (InterruptedException e) {E.PrintStackTrace (); }}}; TIMERTASK TARK1 = new TimeTask () {@Override public void run () {System.out.println (System.CurrentTimemillis ()-inicio); }}; Temporizador temporizador = new Timer (); start = System.CurrentTimemillis (); // iniciar una tarea programada, ejecutar Timer.schedule (tarea, 1000); // Iniciar una tarea programada, ejecutar Timer.Schedule (Tarea1,3000); }}Esperamos que el programa anterior se ejecute después de ejecutar la primera tarea después de que la segunda tarea sea 3s, es decir, emitir uno 1000 y uno 3000.
Los resultados de la operación real son los siguientes:
Los resultados de la operación real no son como deseamos. El resultado del mundo es que la segunda tarea se emite después de 4s, es decir, 4001 es de aproximadamente 4 segundos. ¿A dónde fue esa parte del tiempo? Ese tiempo fue ocupado por el sueño de nuestra primera tarea.
Ahora eliminamos Thread.sleep () en la primera tarea; ¿Esta línea de código se ejecuta correctamente? Los resultados de la operación son los siguientes:
Se puede ver que la primera tarea se ejecuta después de 1s, y la segunda tarea se ejecuta después de 3s después de ejecutar la primera tarea.
Esto significa que el temporizador solo crea un hilo único para realizar todas las tareas de temporizador. Si la ejecución de una tarea de temporizador requiere mucho tiempo, causará problemas con la precisión de otras tareas de tiempo.
Análisis del principio de implementación del temporizador
La siguiente es una breve introducción al principio del temporizador. La siguiente figura es una introducción al modelo principal del temporizador:
1.TaskQueue es una cola prioritaria implementada por un montón de árbol binario equilibrado, y cada objeto de temporizador tiene una cola de tarea única en el interior. El método de programación del temporizador de llamadas de subprocesos del usuario es agregar la tarea de TimeTask a la cola TaskQueue. Al llamar al método de programación, el parámetro de retraso largo se usa para indicar cuánto tiempo se retrasa la tarea para ejecutarse.
2. TimerThread es el hilo que realiza una tarea específica. Obtiene la tarea con la menor prioridad de la cola TaskQueue para la ejecución. Cabe señalar que solo después de ejecutar la tarea actual se obtendrá la siguiente tarea de la cola. Independientemente de si hay un tiempo de retraso establecido en la cola, un temporizador solo tiene un hilo de lectura de temporización, por lo que se puede ver que la implementación interna del temporizador es un modelo de consumo único multiproductor.
Desde el modelo de implementación, podemos saber que para explorar el problema anterior, solo necesita observar la implementación de TimerThread. El principal código fuente lógico del método Ejecutar de TimerThread es el siguiente:
public void run () {try {mainloop (); } Finalmente {// alguien mató este hilo, actuando como si el temporizador haya cancelado sincronizado (cola) {newTaskSmayBescheduled = false; queue.clear (); // Eliminar referencias obsoletas}}} private void mainloop () {while (true) {try {timeTask tarea; Tarea booleana con tareas; // bloquear sincronizado (cola) {......} if (taskfired) tareaSe puede ver que cuando se lanza una excepción que no sea InterruptedException durante la ejecución de la tarea, el único hilo del consumidor terminará debido a que lanzar una excepción, y otras tareas que se ejecutarán en la cola se borrarán. Por lo tanto, es mejor usar la estructura de prueba de prueba para atrapar las principales excepciones posibles en el método Ejecutar de TimeTask, y no arroje las excepciones fuera del método Ejecutar.
De hecho, para implementar funciones similares al temporizador, es una mejor opción para usar el horario de ProchuledThreadPoolExecutor. Una tarea en ProchuledThreadPoolExecutor lanza una excepción, y las otras tareas no se ven afectadas.
El ejemplo de ProchuledThreadPoolExecutor es el siguiente:
/*** Creado por Cong el 2018/7/12. */clase pública ProchuledThreadPoolExecutortest {static ProchuledThreadPoolExecutor ProchuledThreadPoolExecutor = new ProchuledThreadPoolExecutor (1); public static void main (string [] args) {ProchuledThreadPoolExeCutor.schedule (new Runnable () {public void run () {System.out.println ("-una tarea ---"); intente {Thread.SeMe (1000);} Catch (InterruptedException e) {E.PrintStacktrace ();} SHOLO NEW SOLT }, 500, TimeUnit.MicroseConds); scheduledThreadPoolExecutor.schedule(new Runnable() { public void run() { for (int i =0;i<5;++i) { System.out.println("--two Task---"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } }, 1000, TimeUnit.Microseconds); ProchuledThreadPoolExecutor.shutdown (); }}Los resultados de la operación son los siguientes:
La razón por la cual otras tareas de ProchuledThreadPoolExecutor no se ven afectadas por la tarea que arroja excepciones es porque la captura deja caer la excepción en la tarea programada de FutureTask en ProchuledThreadPoolExecutor, pero es la mejor práctica usar Catch para capturar excepciones e registros de impresión en el método de ejecución de la tarea del grupo de hilos.