Analysis of Timer mechanism source code

Analysis of Timer mechanism source code

Author: Mr point in time: 2019.3.27

Compelling

Q: How many ways are available for timing and delayed tasks?
A: Handler, Timer, ScheduledThreadPool, AlarmManager

Everyone should be familiar with the Handler mechanism. Today I will talk about Timer, which is rarely asked about. Let's talk about the thread pool another day, it is expected to be Sunday.

The Timer mechanism includes four main core classes: Timer, TaskQueue, TimerThread, and TimerTask. Let's understand one by one.

Timer

When the Timer class is loaded, a new task queue and a new timer thread are created. And bind the two together.

public class Timer {
    private final TaskQueue queue = new TaskQueue();
    private final TimerThread thread = new TimerThread(queue);
}
 

Initialize the Timer

Anyway, it is to set a name for the thread, or set whether it is a daemon thread, and finally start the thread; this thread is TimerThread.

Calling these four methods can perform timing tasks , delay tasks , and periodic execution tasks .

These two methods are very similar to the last two methods above. The difference is that the last parameter of sched() is passed in the current value or the opposite value. The specific impact here will be introduced later. The core code of sched() is:

private void sched(TimerTask task, long time, long period) {
       //
       synchronized(queue) {
          //
          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);
            if (queue.getMin() == task)
                queue.notify();
        }
    }
 

The main thing is to assign the initialized task and then add it to the queue. So far there are two methods in Timer that have not been mentioned.

public void cancel(){   }
public int purge(){  CANCELLED  }
 

TaskQueue

The task queue is actually an array of TimerTask with a size of 128. size represents the number of tasks in the queue. Others are some methods of manipulating this array

int size() {   }
void add(TimerTask task){   fixUp(size) 1 0  }
TimerTask getMin(){   }
TimerTask get(int i){  i  }
void removeMin(){   fixDown(1) }
void quickRemove(int i){   }
void rescheduleMin(long newTime){   fixDown(1) }
boolean isEmpty(){   }
void clear(){   }
void fixUp(int k){  1 }
void fixDown(int k){  2 }
void heapify(){  3 }
 

The three sorting methods will not be further explored here. Here is a question, why the position of the first task added is 1, not 0;

TimerThread

class TimerThread extends Thread {
    boolean newTasksMayBeScheduled = true;  
    private TaskQueue queue
    TimerThread(TaskQueue queue) {
        this.queue = queue;
    }
    public void run() {
        try { mainLoop();
        } finally {
            synchronized(queue) {
                newTasksMayBeScheduled = false;
                queue.clear(); 
            }
        }
    }
 
  1. TimerThread is a Thread.
  2. The binding corresponds to TaskQueen when initializing.
  3. When TimerThread is running normally, it always takes queue messages to perform tasks in a loop. When the thread is killed, or other abnormalities occur, the queue will be emptied. tips: newTasksMayBeScheduled is to mark whether this currently keeps a reference to the timer object. True when there are no more tasks in the queue.

Finally, take a look at the core code in mainLoop():

private void mainLoop() {
        while (true) {
            try {
                synchronized(queue) {
                    //
                    long currentTime, executionTime;
                    task = queue.getMin();
                    synchronized(task.lock) {
                        //
                        currentTime = System.currentTimeMillis();
                        executionTime = task.nextExecutionTime;
                        if (taskFired = (executionTime<=currentTime)) {
                            if (task.period == 0) { 
                                queue.removeMin();
                                task.state = TimerTask.EXECUTED;
                            } else {
                                queue.rescheduleMin(
                                task.period<0 ? currentTime   - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
                    if (!taskFired) 
                        queue.wait(executionTime - currentTime);
                }
                if (taskFired)  
                    task.run();
            } catch(InterruptedException e) {
            }
        }
    }
 

After the thread is running, it has been comparing the latest message with the current time. When the execution time is up, it depends on whether it is a one-time task. If it is a one-time task, change the task status. If it is a periodic task, set a new execution time for the task and then enter the queue. If the execution time has not arrived at the beginning, wait for the current queue. Finally, according to whether the execution time has arrived, the most recent task that was taken out is executed.

tips: When the periodic task reset time, there are two kinds of time, when period<0, currentTime-task.period, when period>0, executionTime + task.period. According to the difference between sched() and scheduleAtFixedRate() in Timer, it can be inferred that the former code indicates that after the current task is executed, the period time is entered. The latter code indicates that the period time is entered when the execution of the current task starts.

TimerTask

TimerTask is an abstract class that implements the Runnable interface. There are four properties and three methods inside.

public abstract class TimerTask implements Runnable {
     final Object lock = new Object(); //
     int state = VIRGIN;//
     long nextExecutionTime;//
     long period = 0;//

     static final int VIRGIN = 0;
     static final int SCHEDULED   = 1;
     static final int EXECUTED    = 2;
     static final int CANCELLED   = 3;
}
 

VIRGIN: Initialize the default value to indicate that this task has not been added to the execution queue.
SCHEDULED: The task is scheduled for execution and has been added to the execution queue.
EXECUTED: The task is being executed or has been executed and has not been cancelled.
CANCELLED: The task has been cancelled

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);
        }
    }
 

You can implement timing tasks by inheriting TimerTask or creating anonymous internal classes, and you can write the actions to be executed in run(). cancel() returns whether the current task status value is SCHEDULED, and then changes its status value to CANCELLED. scheduledExecutionTime() returns the time of the next execution.

Timer and Handler

  • The structure of Timer mechanism is similar to Handler, and the specific processing is different, but they are divided into four major structures.
  • Timer, Handler: main controller
  • TimerTask, Message: message/task
  • TimerQueue, MessageQueue: message/task queue
  • TimerThread, Looper: cyclically fetch messages/tasks
Pros and cons Handler Timer
Perform the same non-periodic task Just need to send another message Need to create a new TimerTask
Communication Flexible communication between threads TimerTask is executed in the child thread
reliability Periodic execution of tasks is more reliable Periodic execution of tasks is unreliable (explained below)
Memory leak Easy to leak Easy to leak
Memory consumption small relatively bigger
flexibility Rely on looper, not flexible Timer does not depend on other classes

Periodic tasks executed by Timer are easily disturbed by itself. (When the time-consuming task is executed in sched(), the execution of the next task will be greatly delayed; when the time-consuming task needs to operate the same object in scheduleAtFixedRate(), the task object cannot be obtained and wait for the last one. The task releases the lock.)

summary

Handler is suitable for most scenarios and is easy to handle. Timer is only suitable for performing repetitive tasks that consume less time. It's no wonder that Timer related articles are so popular that I only know after reading the source code that they are a little spicy chicken. These two days are wasted.

Finally, I hope you will pay more attention to our blog team: Tianxing Technology Blog https://juejin.im/user/3931509310892503