Java timing task Timer scheduler [1] source code analysis (text explanation version)

Let's start with the example of alarm clock (the following sections take alarm clock as an example, and all the source code only lists the key parts).

public class ScheduleDemo {
 
    public static void main(String[] args) throws InterruptedException {
        long delay = 1000;  // Start in a second
        long period = 2000; // Execution interval
        Timer timer = new Timer();
        AlarmTask alarm = new AlarmTask("Alarm clock 1");
        log.info("["+Thread.currentThread().getName()+"]Turn on alarm scheduling!");
        timer.schedule(alarm,delay,period);
    }
 
    /**
     *     Analog alarm clock
     */
    static class AlarmTask extends TimerTask{
        String name ;
        public AlarmTask(String name){
            this.name=name;
        }
        @Override
        public void run() {
            log.info("["+Thread.currentThread().getName()+"]"+name+":Tick.");
            Thread.sleep(1000); //Simulate the execution time of alarm clock, omit exceptions...
        }
    }
}

After one second, the alarm clock will run every two seconds.

[main] turn on alarm scheduling!
[Timer-0] alarm 1: beep...
[Timer-0] alarm 1: beep...
[Timer-0] alarm 1: beep...

As can be seen from the printing results, alarm scheduling and execution are not a thread.

Here is the Timer sequence diagram. You can understand the general process of Timer.

Let's start to analyze the Timer source code.

public class Timer {
 
    private final TaskQueue queue = new TaskQueue();
 
    private final TimerThread thread = new TimerThread(queue);
 
    public Timer() {
        this("Timer-" + serialNumber());
    }
 
    public Timer(String name) {
        thread.setName(name);
        thread.start();
    }

As you can see, an internal thread and queue are maintained in Timer, and they are initialized when Timer is instantiated. When initializing Timer, the internal thread TimerThread starts. The following is the execution process of TimerThread.

class TimerThread extends Thread {
   
    public void run() {
        mainLoop();
    }
 
    private void mainLoop() {
        while (true) {
                synchronized(queue) {
                    // Queue empty, thread blocked
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait();

As you can see, although the thread TimerThread has started, the thread is blocked (waiting for the queue lock) because the queue is empty.

The above is the whole running process of Timer timer = new Timer(). Continue to see timer.schedule(alarm,delay,period).

public class Timer {
    
    public void schedule(TimerTask task, long delay, long period) {
        sched(task, System.currentTimeMillis()+delay, -period);
    }
 
    private void sched(TimerTask task, long time, long period) {
        synchronized(queue) {
               synchronized(task.lock) {
                  task.nextExecutionTime = time;
                  task.period = period;
                  task.state = TimerTask.SCHEDULED;
                }
           // Queue alarm
            queue.add(task);
            // The main thread releases the queue lock and wakes up the TimerThread
            if (queue.getMin() == task)
                queue.notify();
        }
    } 

From the source code, it can be seen that the main thread just meets the requirement of queue.getMin() == task. At this time, the TimerThread thread (waiting) will wake up and the queue lock will be released.

Next, switch to the running scenario of TimerThread.

private void mainLoop() {
    while (true) {
            synchronized(queue) {
                while (queue.isEmpty() && newTasksMayBeScheduled)
                    queue.wait();
                if (queue.isEmpty())
                    break;
                task = queue.getMin();
                synchronized(task.lock) {
                    currentTime = System.currentTimeMillis();
                    executionTime = task.nextExecutionTime;
                    // It's time to execute
                    if (taskFired = (executionTime<=currentTime)) {
                        // ... redefining the execution time of the next alarm clock
                    }
                }
                if (!taskFired)
                    // Execution time is not up, thread is blocked again
                    queue.wait(executionTime - currentTime);
            }
            if (taskFired)
                task.run(); // Synchronize user defined alarms
    }
}

According to the above source code analysis, after the timer thread is woken up, the execution time will be determined. When the time is up, the execution time of the next alarm clock will be initialized and the alarm clock will run. Otherwise, the thread will wait for the specified time.

This goes on and on..

Keywords: Programming

Added by francisexpress on Mon, 09 Dec 2019 13:48:00 +0200