JDK timer Timer principle

Preface

Some time ago, I thought of using redis to implement a delay queue, but the underlying timer is more complicated than how to implement it, so I studied jdk's Timer.

Timer is a class used to execute scheduled tasks, which can be executed once or cyclically at specified time intervals (until active cancel or the thread is killed). Task processing in Timer adopts the design idea of ​​the producer-consumer model.

Brief description of principle

Insert image description here

Timer maintains a TimerThread (inherits Thread) and TaskQueue (actually a TimerTask array with an initial length of 128 [TimerTask is our execution task]). When we new Timer(), the TimerThread.start method will be called. Then the TimerThread thread while(true) loop takes out the task (TimerTask) from the TaskQueue for execution. So how to take out the task with the latest execution time? As mentioned earlier, the bottom layer of TaskQueue is an array, but it is actually a small root heap. As long as the root element is taken out each time, it is the task with the latest execution time.

ps: There is a long type nextExecutionTime (next execution time) variable in TimerTask. The small root heap is sorted based on this...
Taking out the most recent task here is not The root element of the small root heap is taken, but the element with subscript 1 in TaskQueue, which is the second element

Component introduction

Timer key attributes: TaskQueue, TimerThread

TaskQueue: task queue

A task pool TaskQueue that stores tasks, including a TimerTask array with an initial size of 128, which is responsible for task storage (add), sorting (fixUp, fixDown), removal (getMin), cleanup (removeMin, quickRemove), Loop task processing (rescheduleMin) and some other basic operations. And through sorting, it is guaranteed that the execution of the task at the head of the team must be the earliest.
According to the comments, you can know that TimerTask is a model used as a balanced binary tree. The two child nodes mounted under a parent node array[n] are array[2n] and array[2n+1] .
Insert image description here

TimerTask: task entity

TimerTask is a task entity and an implementation class of the Runnable interface. It contains a lock for thread safety, a field state for marking task status, and a task content abstract method run() for users to implement.

public abstract class TimerTask implements Runnable {
    
    
    /** 此对象用于控制对TimerTask内部的访问。 */
    final Object lock = new Object();

    /** 标记任务状态的字段 初始化为0 */
    int state = VIRGIN;

    /** 该状态表示任务尚未执行。在TimerTask对象创建时,它的状态就是VIRGIN。*/
    static final int VIRGIN = 0;
    /** 
     * 表示任务已经被调度,等待执行。当调用Timer.schedule()方法成功后,任务的状态将变为SCHEDULED。
     * 此时任务已经被加入了任务队列中,等待Timer线程按照任务的调度时间来执行。
     */
    static final int SCHEDULED   = 1;
    /** 表示任务已经执行完成。此时任务的run()方法已经执行完毕,但任务对象还没有从任务队列中删除,因为队列中的任务删除是由Timer线程自动完成的。*/
    static final int EXECUTED    = 2;
    /** 
     * 该状态表示任务已经被取消。当调用TimerTask.cancel()方法取消任务时,任务的状态将变为CANCELLED。
     * 此时任务被标记为取消状态,即使它已经被加入了任务队列中,也不会执行。一旦任务被标记为CANCELLED状态,它将永远不会被执行。
     */
    static final int CANCELLED   = 3;

    /**
     * 此任务的下一次执行时间,格式为System.currentTimeMillis,假设此任务计划执行。对于重复任务,此字段在每次任务执行之前更新。
     */
    long nextExecutionTime;
    /**
     * 重复任务的周期(毫秒)。正值表示固定利率执行。负值表示固定延迟执行。值0表示非重复任务。
     */
    long period = 0;
    // ...
TimerThread: event consumer

A TimerThread as an event consumer. The TimerThread continuously obtains the head task of the current task queue and executes the task. And depending on whether the task needs to be cycled, decide whether to remove the task or re-add the task to the task queue according to the next execution time. When continuously acquiring tasks to be executed in TimerThread, the mechanisms of Object.wait() and Object.notify() are used. Object.wait() ensures that resources are released in time when the task queue is empty, and when there are new tasks, task traversal is resumed in time through Object.notify().
Insert image description here
Timer itself provides an interface for encapsulation, instantiation and external exposure of running tasks for the above three operations. At the same time, as a producer, it adds user tasks to the task queue; at the consumer level, Timer is also uniquely bound to the consumer thread and is responsible for starting the consumer thread and notifying dormant consumers in time after new tasks are produced. By. Provides multiple construction methods and cleanup interfaces.

Source code analysis

Timer usage demo

public class TimerDemo {
    
    

    public static void main(String[] args) {
    
    
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
    
    
            @Override
            public void run() {
    
    
                String strDate = format.format(new Date());
                try {
    
    
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
    
     }
                System.err.println("strDate1 = " + strDate);
            }
        }, 5, 5000);
    }

}

Timer construction method:

// Timer.java  131行
public Timer() {
    
    
    this("Timer-" + serialNumber());
}
// Timer.java  158行
public Timer(String name) {
    
    
    // 设置线程名称
    thread.setName(name);
    // 启动TimerThread线程
    thread.start();
}

TimerThread inherits the Thread class, so let's take a look at its run method:

// Timer.java   503行
public void run() {
    
    
    try {
    
    
        mainLoop();
    } finally {
    
    
        // Someone killed this Thread, behave as if Timer cancelled
        // 【有人杀死了这个线程,表现得就像计时器被取消一样】
        synchronized(queue) {
    
    
            newTasksMayBeScheduled = false;
            queue.clear();  // Eliminate obsolete references【消除过时的引用】
        }
    }
}

Follow up with the mainLoop() method

/**
 * 主计时器循环
 */
 // Timer.java   518行
private void mainLoop() {
    
    
    while (true) {
    
    
        try {
    
    
            TimerTask task;
            boolean taskFired;
            synchronized(queue) {
    
    
                // 线程为空则wait
                while (queue.isEmpty() && newTasksMayBeScheduled)
                    queue.wait();
                if (queue.isEmpty())
                    break; // 队列为空,将永远保留;死亡
                    
                long currentTime, executionTime;
                // 获取距离执行时间最近的任务(返回的是queue[1]的引用)
                task = queue.getMin();
                synchronized(task.lock) {
    
    
                    // 如果任务的状态是已经取消,则移除该任务,移除后TimerTask数组元素将重新排序
                    if (task.state == TimerTask.CANCELLED) {
    
    
                        queue.removeMin();
                        continue;  // No action required, poll queue again
                    }
                    currentTime = System.currentTimeMillis();
                    // 该任务的下次执行时间
                    executionTime = task.nextExecutionTime;
                    // 若任务的下次执行时间 < 当前时间
                    if (taskFired = (executionTime<=currentTime)) {
    
    
                        // period值0表示非重复任务。将该任务从任务队列移除,并且将任务的state设置为已执行
                        if (task.period == 0) {
    
     // Non-repeating, remove
                            queue.removeMin();
                            task.state = TimerTask.EXECUTED;
                        } else {
    
     // Repeating task, reschedule
                            // 将与头任务关联的nextExecutionTime设置为指定值,并相应地调整优先级队列。
                            queue.rescheduleMin(
                              task.period<0 ? currentTime   - task.period
                                            : executionTime + task.period);
                        }
                    }
                }
                // 还未到执行时间,wait
                if (!taskFired)
                    queue.wait(executionTime - currentTime);
            }
            // 执行时间到了,执行任务的run方法【这里就是我们自己写的逻辑了】
            if (taskFired)
                task.run();
        } catch(InterruptedException e) {
    
    
        }
    }
}

As above, if the queue is empty & newTasksMayBeScheduled is true, call the wait method to wait. If the queue is not empty, call queue.getMin to get the task closest to the execution time (returns a reference to queue[1]), and then Determine the state and nextExecutionTime (next execution time) of the task. If the conditions are met, the task will be executed; if the state of the task has been canceled, the task will be removed. After removal, the elements of the TimerTask array will be reordered. If the execution time has not yet arrived, wait.

ps:Why does queue.getMin return the queue[1] element?
Answer: TaskQueue uses an array queue internally to store all TimerTask objects. The first element of the array, queue[0], is not used, and the actual task is stored starting from the second element of the array, queue[1]. The elements in the array are sorted from small to large according to the execution time of the task. In other words, the queue[1] element represents the first task to be executed.

Next, let’s look at the Timer.schedule() method:

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.");
    // 进去看看,参数1:任务 参数二:下次执行时间 参数三:重复任务的周期(毫秒)
    sched(task, System.currentTimeMillis()+delay, -period);
}

// Timer.java   386行
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");
            // 设置任务下次执行时间、任务周期、任务状态
            task.nextExecutionTime = time;
            task.period = period;
            task.state = TimerTask.SCHEDULED;
        }
        // 将任务添加到队列【必要的时候会对任务队列进行扩容,对小根堆的元素重新排序】
        queue.add(task);
        // 若当前添加的任务是距离当前执行时间最近的任务则唤醒等待线程【其实就是TimerThread线程】
        if (queue.getMin() == task)
            queue.notify();
    }
}

There are a few final points to note:
1) When not in use, be sure to cancel in time to release resources.
2) When there are multiple tasks in the timer, because the subsequent tasks will depend on the completion of the previous tasks, especially if there are time-consuming tasks, timing inaccuracies will occur.
3) When there are multiple tasks, if one of them terminates due to an exception, the execution of all tasks will exit (the consumer thread is terminated abnormally)
4) Timer relies on system time when executing periodic tasks. If the current system time changes, there will be some execution changes. ScheduledExecutorService is based on time delay and will not change execution due to changes in system time.

Guess you like

Origin blog.csdn.net/RookiexiaoMu_a/article/details/129123430