timer is an early API in JDK. We usually use Timer and TimerTask to do tasks with delays and periodicity before newScheduledThreadPool comes out, but Timer has some flaws. Why do we say so?
Timer creates only unique threads to perform all Timer tasks. If the execution of a timer task is time-consuming, it will cause problems with the accuracy of other TimerTasks. For example, one TimerTask is executed every 10 seconds, and another TimerTask is executed every 40ms. The repeated tasks will be called 4 times in a row after the subsequent tasks are completed, or 4 calls are completely "lost". Another problem with Timer is that if TimerTask throws an unchecked exception, it will terminate the timer thread. In this case, the Timer will not reply to the thread's execution again; it mistakenly believes that the entire Timer has been cancelled. TimerTask, which has been scheduled but has not yet been executed, will never be executed again, and new tasks cannot be scheduled.
Here I made a small demo to reproduce the problem, the code is as follows:
package com.hjc;import java.util.Timer;import java.util.TimerTask;/** * Created by cong on 2018/7/12. */public class TimerTest { //Create timer object static Timer timer = new Timer(); public static void main(String[] args) { //Add task 1, delay execution timer.schedule(new TimerTask() { @Override public void run() { System.out.println("--one Task---"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } throw new RuntimeException("error "); } }, 500); //Add task 2, delay execution timer.schedule(new TimerTask() { @Override public void run() { for (;;) { System.out.println("--two Task---"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } } }, 1000); }}As mentioned above, a task was first added to execute after 500ms, and then the second task was added to execute after 1s. What we expect is that when the first task outputs ---one Task --- and waits for 1s, the second task will output--two Task---,
However, after executing the code, the output is as follows:
Example 2,
public class Shedule { private static long 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(); //Start a scheduled task, execute timer.schedule(task,1000); //Start a scheduled task, execute timer.schedule(task1,3000); }}We expect that the above program will be executed after the first task is executed after the second task is 3S, that is, output one 1000 and one 3000.
The actual operation results are as follows:
The actual operation results are not as we wish. The result of the world is that the second task is output after 4S, that is, 4001 is about 4 seconds. Where did that part of the time go? That time was occupied by the sleep of our first task.
Now we remove Thread.sleep() in the first task; is this line of code running correctly? The operation results are as follows:
It can be seen that the first task is executed after 1S, and the second task is executed after 3S after the first task is executed.
This means that Timer only creates a unique thread to perform all Timer tasks. If the execution of a timer task is time-consuming, it will cause problems with the accuracy of other TimerTasks.
Timer implementation principle analysis
The following is a brief introduction to the principle of Timer. The following figure is an introduction to the principle model of Timer:
1.TaskQueue is a priority queue implemented by balanced binary tree heap, and each Timer object has a unique TaskQueue queue inside. The schedule method of the user thread calling timer is to add the TimerTask task to the TaskQueue queue. When calling the schedule method, the long delay parameter is used to indicate how long the task is delayed to be executed.
2. TimerThread is the thread that performs a specific task. It obtains the task with the least priority from the TaskQueue queue for execution. It should be noted that only after the current task is executed will the next task be obtained from the queue. Regardless of whether there is a set delay time in the queue, a Timer only has one TimerThread thread, so it can be seen that the internal implementation of Timer is a multi-producer single-consumer model.
From the implementation model, we can know that to explore the above problem, you only need to look at the implementation of TimerThread. The main logical source code of the run method of TimerThread is as follows:
public void run() { try { mainLoop(); } finally { // Someone killed this thread, acting as if Timer has cancelled synchronized(queue) { newTasksMayBeScheduled = false; queue.clear(); // Eliminate outdated references} }} private void mainLoop() { while (true) { try { TimerTask task; boolean taskFired; // Lock synchronized(queue) { ...... } if (taskFired) task.run();//Execute task} catch(InterruptedException e) { } } }It can be seen that when an exception other than InterruptedException is thrown during task execution, the only consumer thread will terminate because of throwing an exception, and other tasks to be executed in the queue will be cleared. Therefore, it is best to use the try-catch structure to catch the main possible exceptions in the run method of TimerTask, and do not throw the exceptions outside the run method.
In fact, to implement functions similar to Timer, it is a better choice to use ScheduledThreadPoolExecutor's schedule. One task in ScheduledThreadPoolExecutor throws an exception, and the other tasks are not affected.
The ScheduledThreadPoolExecutor example is as follows:
/** * Created by cong on 2018/7/12. */public class ScheduledThreadPoolExecutorTest { static ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1); public static void main(String[] args) { scheduledThreadPoolExecutor.schedule(new Runnable() { public void run() { System.out.println("--one Task---"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } throw new RuntimeException("error "); } }, 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); scheduledThreadPoolExecutor.shutdown(); }}The operation results are as follows:
The reason why other tasks of ScheduledThreadPoolExecutor are not affected by the task that throws exceptions is because the catch drops the exception in the ScheduledFutureTask task in ScheduledThreadPoolExecutor, but it is best practice to use catch to catch exceptions and print logs in the run method of the thread pool task.