This is the 20th day of my participation in the August Text Challenge.More challenges in August

background

Based on the previous article, a fatal flaw was found in the NTP time synchronization function. The system time was changed to a future time, and then the timer was started. At this time, if the system time was changed back to the current correct time, it was found that the timer hung and did not work. Then search for information, found that is the reason of timer internal implementation.

Cause analysis,

For this problem, we can simply trace the source code of the Timer. There are two important objects in the Timer, one is TaskQueue, the other is TimerThread. A TaskQueue is a queue that contains tasks that are passed when we call timer.schedule. TimerThread is a Thread that inherits Thread and calls a function called mainLoop() in its run method:

public void run() {
   try {
       mainLoop();
   } finally {
       // Someone killed this Thread, behave as if Timer cancelled
       synchronized(queue) {
           newTasksMayBeScheduled = false;
           queue.clear();  // Eliminate obsolete references
       }
   }
}
Copy the code

Take a look at the mainLoop code:

/** * The main timer loop. (See class comment.) */ private void mainLoop() { while (true) { try { TimerTask task; boolean taskFired; 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; task = queue.getMin(); synchronized(task.lock) { if (task.state == TimerTask.CANCELLED) { queue.removeMin(); continue; // No action required, poll queue again } currentTime = System.currentTimeMillis(); executionTime = task.nextExecutionTime; if (taskFired = (executionTime<=currentTime)) { 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); } if (taskFired) // Task fired; run it, holding no locks task.run(); } catch(InterruptedException e) { } } }Copy the code

While (true) appears in the code, indicating that the execution has been repeated, our task is placed in a queue, and the mainLoop method is to continuously execute all the tasks in the queue.

Look at lines 49, 50, and 51:

CurrentTime = system.currentTimemillis (); // executionTime = task.nextexecutionTime; taskFired = (executionTime<=currentTime)Copy the code

If the next time to execute the task is less than or equal to the currentTime, the time to execute the task is up, proceed down, if not, taskFired=false, then wait, executiontime-currenttime. So if we set the system time to a future time, then executionTime<=currentTime must be true, execute the next task, and the program will work fine.

ExecutionTime <=currentTime is false, queue. Wait (executionTime-currentTime) is executed, and the thread is suspended. The wait time is obviously executionTime-currentTime.

The solution

Good thing Android provides ScheduledFuture. Simple usage:

import android.util.Log; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; public class TimerUtil { private ScheduledExecutorService service; private ScheduledFuture<? > future; public void startTimer(Runnable runable, int delay,int period) { if (service == null) { service = Executors.newScheduledThreadPool(1); / / thread space future = service. ScheduleAtFixedRate (runable, delay, period, TimeUnit. MILLISECONDS); } } public void cancelTimer() { Log.d("TimerUtil", "cancelTimer"); if (future ! = null) { future.cancel(true); future = null; } service = null; }}Copy the code

The code above can be used as a utility class instead of a Timer.