Timer timer source code analysis

Timer timer source code analysis

One Timer

1,Timer

Compared with Quartz, Timer has a relatively simple structure, its principle is easier to move, and the two will have similarities. It may be easier to understand Quartz after understanding Timer. Before Quartz, learn about Timer timer. The following is the introduction in JDK Api:

  • The function of a thread to schedule tasks for future execution in a background thread. Tasks can be scheduled to be executed once or repeated regularly.
  • Corresponding to each timer object is a single background thread, which is used to execute all tasks of all timers in turn. Timer tasks should be completed quickly. If a timer task takes a lot of time to complete, it will "time" the timer's task execution thread. This may delay the execution of subsequent tasks that may be "tied up" and executed quickly when (and) if the offending task is finally completed.
  • After the last reference to the Timer object *, all outstanding tasks have been completed, The task execution thread of the Timer terminates normally (and collected into garbage collection). However, this may take any long time to happen. By default, the task execution thread does not run as a daemon thread *, so it can terminate the application. If the caller wants to quickly terminate the task execution thread of the Timer, the caller should call the cancel method of the Timer
  • This class is thread safe: multiple threads can share a single Timer object without external synchronization.
  • If the task execution thread of the timer terminates unexpectedly, for example, because it calls the stop method, any further attempt to schedule the task on the timer will produce an IllegalStateException, just as the cancel method of the timer is called.

2. Source code analysis

2.1 Timer

When initializing the Timer, the TaskQueue and TimerThread will be initialized accordingly, and the thread will be started to make the thread in a wait state.

   /**
     * The timer task queue.  This data structure is shared with the timer
     * thread.  The timer produces tasks, via its various schedule calls,
     * and the timer thread consumes, executing timer tasks as appropriate,
     * and removing them from the queue when they're obsolete.
     */
    private final TaskQueue queue = new TaskQueue();
	/**
     * The timer thread.
     */
    private final TimerThread thread = new TimerThread(queue);
	/**
     * Creates a new timer whose associated thread has the specified name.
     * The associated thread does <i>not</i>
     * {@linkplain Thread#setDaemon run as a daemon}.
     *
     * @param name the name of the associated thread
     * @throws NullPointerException if {@code name} is null
     * @since 1.5
     */
	//During initialization, thead thread will be started and put in a wait state.
    public Timer(String name) {
        thread.setName(name);
        thread.start();
    }
	/**
     * Schedules the specified task for execution after the specified delay.
     *
     * @param task  task to be scheduled.
     * @param delay delay in milliseconds before task is to be executed.
     * @throws IllegalArgumentException if <tt>delay</tt> is negative, or
     *         <tt>delay + System.currentTimeMillis()</tt> is negative.
     * @throws IllegalStateException if task was already scheduled or
     *         cancelled, timer was cancelled, or timer thread terminated.
     * @throws NullPointerException if {@code task} is null
     */
    public void schedule(TimerTask task, long delay) {
        if (delay < 0)
            throw new IllegalArgumentException("Negative delay.");
        sched(task, System.currentTimeMillis()+delay, 0);
    }

The core schedule method provides multiple overloaded methods to users, and will eventually call the sched method. The parameter meanings are as follows: task (specific execution action), time (next execution time), perid (each execution interval).

 	/**
     * Schedule the specified timer task for execution at the specified
     * time with the specified period, in milliseconds.  If period is
     * positive, the task is scheduled for repeated execution; if period is
     * zero, the task is scheduled for one-time execution. Time is specified
     * in Date.getTime() format.  This method checks timer state, task state,
     * and initial execution time, but not period.
     
     * Schedules the specified timer task for execution at the specified time and for the specified time period, in milliseconds.
     * If the period is positive, the task is planned to be executed repeatedly; If the period is zero, the task is scheduled to be executed at one time.
     * The time is specified by date. getTime() format. This method checks the timer status, task status, and initial execution time, but does not check the cycle.
     *
     * @param task Perform specific actions.
     * @param time Next execution time, timestamp.
     * @param period Each execution interval, timestamp.
     */
    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.
        // Limit the maximum value of the time interval
        if (Math.abs(period) > (Long.MAX_VALUE >> 1))
            period >>= 1;
		//Increasing lock when sched is called concurrently by the same Timer
        synchronized(queue) {
            if (!thread.newTasksMayBeScheduled)
                throw new IllegalStateException("Timer already cancelled.");
			//When the same task is called by different timers, the task lock application, task Lock is the lock ID in the Timer Task class.
            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;
            }
			//Add the task to the execution plan queue.
            queue.add(task);
            if (queue.getMin() == task)
                //getMin() gets the next execution in the execution plan. If the current task is the next execution, it will notify the queue and do not need to wait
                queue.notify();
        }
    }

2.2 TimerThread(thread)

TimerThread is mainly used to manage the activation of duplicate tasks and the deletion of non duplicate tasks. In the above Timer, the following threads will be started during initialization to make the thread reach the running state, execute the mainLoop method, and then reach the waiting state in this method.

	/**
     * This flag is set to false by the reaper to inform us that there
     * are no more live references to our Timer object.  Once this flag
     * is true and there are no more tasks in our queue, there is no
     * work left for us to do, so we terminate gracefully.  Note that
     * this field is protected by queue's monitor!
     */
	//Whether to continue execution. true indicates to continue, false indicates to end, and finally ends the timer. You also need to clear the execution plan queue.
    boolean newTasksMayBeScheduled = true;
	/**
     * Our Timer's queue.  We store this reference in preference to
     * a reference to the Timer so the reference graph remains acyclic.
     * Otherwise, the Timer would never be garbage-collected and this
     * thread would never go away.
     */
    private TaskQueue queue;

    TimerThread(TaskQueue queue) {
        this.queue = queue;
    }

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

    /**
     * The main timer loop.  (See class comment.)
     */
    private void mainLoop() {
        while (true) {
            //?????? The wireless execution for loop completes the automatic service.		
            try {
                TimerTask task;
                boolean taskFired;
                //When an execution plan queue is, multiple timerthreads are initialized, and threads are started, the queue needs to be locked
                synchronized(queue) {
                    // Wait for queue to become non-empty
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        //When the five executable tasks in the execution plan are in executable status, the execution plan waits for the task to be added
                        queue.wait();
                    if (queue.isEmpty())
                        //If the queue is empty, the exit is not in progress. During clear
                        break; // Queue is empty and will forever remain; die

                    // Queue nonempty; look at first evt and do the right thing
                    //executionTime next execution time
                    long currentTime, executionTime;
                    //Get next execution plan
                    task = queue.getMin();
                    //Scenario when the same task is listed in multiple execution plans,
                    synchronized(task.lock) {
                        if (task.state == TimerTask.CANCELLED) {
                            //When the task status is cancelled, the queue execution plan needs to delete the task and sort the queue according to the next execution time.
                            queue.removeMin();
                            continue;  // No action required, poll queue again
                        }
                        currentTime = System.currentTimeMillis();
                        executionTime = task.nextExecutionTime;
                        if (taskFired = (executionTime<=currentTime)) {
                            //taskFired (true) when the next execution time is less than or equal to the current time
                            if (task.period == 0) { // Non-repeating, remove
                                //When there is no time interval and the next execution time is less than or equal to the current time, identify a non repetitive task and delete it from the execution plan
                                queue.removeMin();
                                //And update the execution status of the task
                                task.state = TimerTask.EXECUTED;
                            } else { // Repeating task, reschedule
                                //If the task task is changed to repeat, the next execution time will be updated, and the queue execution plan will be sorted by the next execution time.
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime   - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
                    if (!taskFired) // Task hasn't yet fired; wait
                        //taskFired (false) wait when the next execution time is greater than the current time
                        queue.wait(executionTime - currentTime);
                }
                if (taskFired)  // Task fired; run it, holding no locks
                    //If the conditions are met, execute the task
                    task.run();
            } catch(InterruptedException e) {
            }
        }
    }

2.3 TaskQueue(queue)

    /**
     * Priority queue represented as a balanced binary heap: the two children
     * of queue[n] are queue[2*n] and queue[2*n+1].  The priority queue is
     * ordered on the nextExecutionTime field: The TimerTask with the lowest
     * nextExecutionTime is in queue[1] (assuming the queue is nonempty).  For
     * each node n in the heap, and each descendant of n, d,
     * n.nextExecutionTime <= d.nextExecutionTime.
     */
    //Default array length 128
	private TimerTask[] queue = new TimerTask[128];
	/**
     * Adds a new task to the priority queue.
     */
	//Judge the length and expand the capacity of the array
    void add(TimerTask task) {
        // Grow backing store if necessary
        if (size + 1 == queue.length)
            queue = Arrays.copyOf(queue, 2*queue.length);

        queue[++size] = task;
        fixUp(size);
    }

    /**
     * Return the "head task" of the priority queue.  (The head task is an
     * task with the lowest nextExecutionTime.)
     */
    TimerTask getMin() {
        return queue[1];
    }
	/**
     * Remove the head task from the priority queue.
     */
    void removeMin() {
        queue[1] = queue[size];
        queue[size--] = null;  // Drop extra reference to prevent memory leak
        fixDown(1);
    }
	/**
     * Sets the nextExecutionTime associated with the head task to the
     * specified value, and adjusts priority queue accordingly.
     */
    void rescheduleMin(long newTime) {
        queue[1].nextExecutionTime = newTime;
        fixDown(1);
    }
	/**
     * Establishes the heap invariant (described above) assuming the heap
     * satisfies the invariant except possibly for the leaf-node indexed by k
     * (which may have a nextExecutionTime less than its parent's).
     *
     * This method functions by "promoting" queue[k] up the hierarchy
     * (by swapping it with its parent) repeatedly until queue[k]'s
     * nextExecutionTime is greater than or equal to that of its parent.
     */
	//Compare the execution plan queue[k] with queue[1], and put the one in front of the execution plan into queue[1]
    private void fixUp(int k) {
        while (k > 1) {
            int j = k >> 1;
            if (queue[j].nextExecutionTime <= queue[k].nextExecutionTime)
                break;
            TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
            k = j;
        }
    }

    /**
     * Establishes the heap invariant (described above) in the subtree
     * rooted at k, which is assumed to satisfy the heap invariant except
     * possibly for node k itself (which may have a nextExecutionTime greater
     * than its children's).
     *
     * This method functions by "demoting" queue[k] down the hierarchy
     * (by swapping it with its smaller child) repeatedly until queue[k]'s
     * nextExecutionTime is less than or equal to those of its children.
     */
    private void fixDown(int k) {
        int j;
        while ((j = k << 1) <= size && j > 0) {
            if (j < size && queue[j].nextExecutionTime > queue[j+1].nextExecutionTime)
                j++; // j indexes smallest kid
            if (queue[k].nextExecutionTime <= queue[j].nextExecutionTime)
                break;
            TimerTask tmp = queue[j];  queue[j] = queue[k]; queue[k] = tmp;
            k = j;
        }
    }

Note: since Timer is executed by single thread and thread pool is not used, there will be errors in time calibration in case of long-time Job work

Keywords: Java Back-end

Added by Gazan on Tue, 28 Dec 2021 22:22:10 +0200