Java timed task - Timer principle
outline
The Jdk library comes with two technologies to realize timed tasks. One is through Timer, and the other is through ScheduledThreadPoolExecutor. The following is an analysis of the principle of Timer implementation.
1, Timer
1. Timer usage
public class TimerTest extends TimerTask { @Override public void run() { System.out.println("test1 --------- " + Thread.currentThread()); } static class TimerDemo { public static void main(String[] args) { Timer timer = new Timer(); // The run method in TimerTest is executed after three seconds and every 1 second timer.schedule(new TimerTest(), 3000, 1000); } } }
2. Source code analysis
After seeing the above practice, do you think it's very simple? Then we analyze the above code.
First, notice that the TimerTest class inherits TimerTask
TimerTask
public abstract class TimerTask implements Runnable { /** * This object is used to control access to the TimerTask internals. */ final Object lock = new Object(); /** * The state of this task, chosen from the constants below. */ int state = VIRGIN; /** * This task has not yet been scheduled. */ static final int VIRGIN = 0; /** * This task is scheduled for execution. If it is a non-repeating task, * it has not yet been executed. */ static final int SCHEDULED = 1; /** * This non-repeating task has already executed (or is currently * executing) and has not been cancelled. */ static final int EXECUTED = 2; /** * This task has been cancelled (with a call to TimerTask.cancel). */ static final int CANCELLED = 3; /** * Next execution time for this task in the format returned by * System.currentTimeMillis, assuming this task is scheduled for execution. * For repeating tasks, this field is updated prior to each task execution. */ long nextExecutionTime; /** * Period in milliseconds for repeating tasks. A positive value indicates * fixed-rate execution. A negative value indicates fixed-delay execution. * A value of 0 indicates a non-repeating task. */ long period = 0; /** * Creates a new timer task. */ protected TimerTask() { } /** * The action to be performed by this timer task. */ public abstract void run(); public boolean cancel() { synchronized(lock) { boolean result = (state == SCHEDULED); state = CANCELLED; return result; } } public long scheduledExecutionTime() { synchronized(lock) { return (period < 0 ? nextExecutionTime + period : nextExecutionTime - period); } } }
After removing the comments, the code of this class will be dozens of lines. You can see that TimerTask implements the Runnable interface and has an abstract run() method. Our TimerTest inherits and implements the run() method. And this class has several member variables:
Lock: used to lock threads
state: indicates the status of the task
nextExecutionTime: the time of the next task execution
Period: the period during which the task is executed
There are only two ways:
cancel(): cancels the task and changes the state of the task
scheduledExecutionTime(): sets the next execution time
Timer
Let's analyze the Timer class. First attach a mind map to facilitate memory and understanding
The following is a brief list of the key codes of Timer. You can check the specific codes yourself
public class Timer { /** It is used to store the tasks to be executed and maintain the queue through the minimum heap**/ private final TaskQueue queue = new TaskQueue(); /** The thread responsible for executing the task**/ private final TimerThread thread = new TimerThread(queue); /** The constructor sets whether the thread name is a daemon and finally starts the thread**/ public Timer(String name, boolean isDaemon) { thread.setName(name); thread.setDaemon(isDaemon); thread.start(); } }
Let's take a look at the TimerThread class
TimerThread
class TimerThread extends Thread { boolean newTasksMayBeScheduled = true; private TaskQueue queue; public void run() { try { mainLoop(); } finally { // Someone killed this Thread, behave as if Timer cancelled synchronized(queue) { newTasksMayBeScheduled = false; queue.clear(); // Eliminate obsolete references } } } /** Key code execution scheduled tasks**/ private void mainLoop() { while (true) { try { TimerTask task; boolean taskFired; synchronized(queue) { // If the queue is empty and newtasks maybescheduled is true, the thread enters the waiting state 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; // Gets the earliest task to be executed in the queue task = queue.getMin(); synchronized(task.lock) { if (task.state == TimerTask.CANCELLED) { // If the status is cancelled, the task will be deleted from the queue queue.removeMin(); continue; // No action required, poll queue again } // Get current time currentTime = System.currentTimeMillis(); // Gets the time when the next task is to be executed executionTime = task.nextExecutionTime; // If the task execution time is less than or equal to the current time, it will start execution 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); } } } // Task hasn't yet fired; wait if (!taskFired) queue.wait(executionTime - currentTime); } //If the execution time of the task is up, execute the task if (taskFired) task.run(); } catch(InterruptedException e) { } } } }
We have a general idea of how TimerThread circularly executes scheduled tasks. Next, let's take a look at Timer's schedule() and sched() methods
schedule & sched
public void schedule(TimerTask task, long delay, long period) { if (delay < 0) throw new IllegalArgumentException("Negative delay."); if (period <= 0) throw new IllegalArgumentException("Non-positive period."); sched(task, System.currentTimeMillis()+delay, -period); } private void sched(TimerTask task, long time, long period) { if (time < 0) throw new IllegalArgumentException("Illegal execution time."); // Get the absolute value of the execution cycle period, if it is greater than half of long MAX_ Value, then divide the period by two 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"); task.nextExecutionTime = time; task.period = period; task.state = TimerTask.SCHEDULED; } // Put in queue queue.add(task); // If the task is added to the queue and left at the top of the heap, the thread will be directly awakened to execute the task (remember to let the thread wait in TimerThread before?) if (queue.getMin() == task) queue.notify(); } }
3. Summary
When we create Timer, we will initialize and start a thread to execute tasks and initialize a priority queue. We use the minimum heap data structure to put the earliest executed task on the top of the heap.
When we call the schedule() method, we put the task in the priority queue. The TimerThread loop judges whether the execution time has been reached. If so, first calculate the next execution time and adjust the heap. Finally, execute the task. If not, the thread enters the wait.