API
The Timer class is used to delay the execution of a task.
- schedule(TimerTask task, **long **delay) to specify the delay time
- Schedule (TimerTask, date time), specify the time
- schedule(TimerTask task, **long **delay, **long **period), specify the delay time and repetition period
- Schedule (TimerTask task, date, firsttime, * * long * * period), specifying the initial time and repetition period
Source code interpretation
There are two main points to be curious about using API:
- How to execute regularly - how to ensure accurate scheduled execution? It seems almost impossible to complete
- For multiple tasks, how to execute subsequent tasks regularly if the current task is not completed.
Unfortunately, after reading the source code, I found that Timer did not give a good answer. It will roughly put the task into the priority queue, the execution time is key, and the earliest polling time will be executed. If the previous task takes too long, subsequent tasks will only be delayed later. Therefore, its solution is very simple. It does not guarantee the accurate time, nor does it guarantee that the task must be carried out according to the specified time.
Overall, it starts a thread subclass object that performs polling operations.
// Timer public Timer(String name) { thread.setName(name); thread.start(); } // TimerThread public void run() { try { mainLoop(); } finally { // Someone killed this Thread, behave as if Timer cancelled synchronized(queue) { newTasksMayBeScheduled = false; queue.clear(); // Eliminate obsolete references } } }
The queue at the bottom of Timer is the smallest heap implemented by the array. Each time a task is added to the queue, the cycle period and the scheduling time nextExecutionTime will be set. To ensure thread safety, it locks the queue and the task itself separately.
private void sched(TimerTask task, long time, long period) { if (time < 0) throw new IllegalArgumentException("Illegal execution time."); // Constrain value of period sufficiently to prevent numeric // overflow while still being effectively infinitely large. if (Math.abs(period) > (Long.MAX_VALUE >> 1)) period >>= 1; synchronized(queue) { if (!thread.newTasksMayBeScheduled) throw new IllegalStateException("Timer already cancelled."); synchronized(task.lock) { if (task.state != TimerTask.VIRGIN) throw new IllegalStateException( "Task already scheduled or cancelled"); // Set time task.nextExecutionTime = time; task.period = period; task.state = TimerTask.SCHEDULED; } queue.add(task); // If the currently added task is the oldest one, try to wake up the polling thread // Because during polling, the queue may be empty and waiting on the queue if (queue.getMin() == task) queue.notify(); } }
The core method of polling thread:
private void mainLoop() { while (true) { try { TimerTask task; boolean taskFired; // Lock polling. If the queue is empty, wait synchronized(queue) { // Wait for queue to become non-empty while (queue.isEmpty() && newTasksMayBeScheduled) queue.wait(); if (queue.isEmpty()) break; // Queue is empty and will forever remain; die // Queue nonempty; look at first evt and do the right thing long currentTime, executionTime; // Get the task that needs to be performed first task = queue.getMin(); synchronized(task.lock) { if (task.state == TimerTask.CANCELLED) { queue.removeMin(); continue; // No action required, poll queue again } // Absolute time as reference currentTime = System.currentTimeMillis(); executionTime = task.nextExecutionTime; // If the predetermined time is less than the current time, the flag is executable if (taskFired = (executionTime<=currentTime)) { // If periodic execution is not required, remove. Otherwise, reset the next time if (task.period == 0) { // Non-repeating, remove queue.removeMin(); task.state = TimerTask.EXECUTED; } else { // Repeating task, reschedule queue.rescheduleMin( task.period<0 ? currentTime - task.period : executionTime + task.period); } } } if (!taskFired) // Task hasn't yet fired; wait queue.wait(executionTime - currentTime); } // implement if (taskFired) // Task fired; run it, holding no locks task.run(); } catch(InterruptedException e) { } } }
shortcoming
After reading the source code, we can understand some of its shortcomings:
1. Firstly, Timer's support for scheduling is based on absolute time rather than relative time, so it is very sensitive to the change of system time.
2. Secondly, the Timer thread will not catch exceptions. If an unchecked exception is thrown by the TimerTask, the Timer thread will terminate. At the same time, the Timer will not resume the execution of the thread. He will mistakenly think that the whole Timer thread will be cancelled. At the same time, TimerTask that has been scheduled but not yet executed will not be executed, and new tasks cannot be scheduled. Therefore, if TimerTask throws an unchecked exception, Timer will produce unpredictable behavior
3. Timer will only create one thread task when executing a scheduled task. If there are multiple threads, if the execution time of a thread task is too long for some reason, exceeding the interval between the two tasks, the execution time of the next task will lag
--------
From Java Concurrent Programming Practice