This is my first article on getting started.

1.0 simple example

public static void main(String[] args) {
	TimerTask timerTask = new TimerTask() {
		@Override
		public void run(a) {
			while (true) {
		        try {
		            Thread.sleep(2000);
		        } catch (Exception e) {
		            // TODO: handle exception
		        }
		        System.out.println("Current time of TimerTask:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(newDate())); }}}; Timer timer =new Timer();
	long delay = 0;
	long period = 1000;
	timer.schedule(timerTask, delay, period);
}
Copy the code

Effect:

How does TimerTask work?

In fact, as you can see from the example above, TimerTask is a class that implements the run method. TimerTask is an abstract class that implements Runnable and provides the abstract run() method.

public abstract class TimerTask implements Runnable {
    // The default value is VIRGIN
    int state = VIRGIN;
    
    / / not executed
    static final int VIRGIN = 0;
    
    // Non-repetitive task, not executed
    static final int SCHEDULED   = 1;
    
    // A non-repetitive task has been executed or is being executed and has not been cancelled
    static final int EXECUTED    = 2;
    
    // Cancelled
    static final int CANCELLED   = 3;
    
    // Next execution time
    long nextExecutionTime;
    
    // Task execution period
    long period = 0;
    
	/** * The action to be performed by this timer task. */
    public abstract void run(a);
    
    The Timer also has a method called Cancel, which will be mentioned in this interview
    public boolean cancel(a) {
        synchronized(lock) {
            boolean result = (state == SCHEDULED);
            state = CANCELLED;
            returnresult; }}ScheduledExecutionTime ()
	
}
Copy the code

The key is Timer 💥

1.1 Constructor for the Timer

  1. Take a look at the third constructor. The body of the method is the name of the thread passed in and set, and then started.
 public Timer(String name) {
     thread.setName(name);
     thread.start();
 }
Copy the code

Here, thread is a thread,

     /** * The timer thread. */
     private final TimerThread thread = new TimerThread(queue);
Copy the code

The TimerThread is defined in the Timer class. As you can see from the comment, this thread is the specific thread used to execute scheduled tasks, executing tasks in the queue when they are triggered.

I’ll cover this TimerThread and TaskQueue in Section 1.3. 😎

 /** * This "helper class" implements the timer's task execution thread, which * waits for tasks on the timer queue, executions them when they fire, * reschedules repeating tasks, and removes cancelled tasks and spent * non-repeating tasks from the queue. */
 class TimerThread extends Thread {}Copy the code

At this point, it’s easy to look back at the other constructors. The first no-argument constructor constructs a thread using a Timer prefix. This is the constructor above, and serialNumber() is the constructor that generates a name.

     public Timer(a) {
         this("Timer-" + serialNumber());
     }
 ​
     /** * This ID is used to generate thread names. */
     private final static AtomicInteger nextSerialNumber = new AtomicInteger(0);
     private static int serialNumber(a) {
         return nextSerialNumber.getAndIncrement();
     }
Copy the code

If you have not understand AtomicInteger () friends can look at this blog, blog.csdn.net/fanrenxiang…

You can see the name of the thread by putting a breakpoint in the code:

  1. The second constructor passes in whether it is a background thread or not. If it is, the main thread terminates automatically without calling cancel.

    public Timer(boolean isDaemon) {
         this("Timer-" + serialNumber(), isDaemon);
     }
Copy the code

A classic example of why you should use background threads is to use threads in a Web application. When you close the web application, the thread is still running! 🐄 🍺

This is because the thread is at the JVM level and is not destroyed when the Web application is shut down. Specific can see this blog post: blog.csdn.net/chetianyao8…

  1. The fourth constructor can set the name and whether it is a background thread, and start.

      public Timer(String name, boolean isDaemon) {
         thread.setName(name);
         thread.setDaemon(isDaemon);
         thread.start();
     }
Copy the code

All right, let’s start with the methods of the Timer, and put the properties in section 1.3

1.2 Timer method

Timer provides six scheduling methods, all of which are very similar, but let’s look at them first and come back to them after section 1.3

     public void schedule(TimerTask task, long delay) {
         if (delay < 0)
             throw new IllegalArgumentException("Negative delay.");
         sched(task, System.currentTimeMillis()+delay, 0);
     }
     
     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);
     }
     
     public void scheduleAtFixedRate(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);
     }
Copy the code

ScheduleAtFixedRate (); scheduleAtFixedRate (); scheduleAtFixedRate (); scheduleAtFixedRate (); You’ll see.

The Sched method is to set up a task, add it to the queue, and perform a notify operation. These will be mentioned later.

     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;  // Next execution time
                 task.period = period;           / / cycle
                 task.state = TimerTask.SCHEDULED;   / / the task status
             }
             // Add to queue
             queue.add(task);
             if(queue.getMin() == task) queue.notify(); }}Copy the code

Cancel () method, and once executed, the Timer stopped. Why newTasksMayBeScheduled to false and why notify() was called also need to see what follows.

    public void cancel(a) {
         synchronized(queue) {
             thread.newTasksMayBeScheduled = false;
             queue.clear();
             queue.notify();  // In case queue was already empty.}}Copy the code

The purge() method, after you cancel the TimerTask multiple times, the queue gets messed up, so you need to call this method, reclaim the space and rearrange it.

Notice that this is not the Timer’s cancel method, it’s the TimerTask’s cancel method.

     public int purge(a) {
          int re0sult = 0;
 ​
          synchronized(queue) {
              for (int i = queue.size(); i > 0; i--) {
                  if(queue.get(i).state == TimerTask.CANCELLED) { queue.quickRemove(i); result++; }}if(result ! =0)
                  queue.heapify();
          }
 ​
          return result;
      }
Copy the code

1.3 Properties of the Timer

In addition to the two properties of the Timer, Thread and nextSerialNumber, there is a queue that stores the task to be scheduled, the TimerTask.

1.3.1 TaskQueue

The structure of a TaskQueue is simple: an array and a size. Note that this queue is stored in queue[1]-queue[size]. The default queue size is 128, but capacity expansion is supported.

 private final TaskQueue queue = new TaskQueue();
     
     class TaskQueue {
         private TimerTask[] queue = new TimerTask[128];
         private int size = 0;
     }
Copy the code

The capacity expansion is performed when the TimerTask is added

    void add(TimerTask task) {
         // Expand here
         if (size + 1 == queue.length)
             queue = Arrays.copyOf(queue, 2*queue.length);
 ​
         queue[++size] = task;
         fixUp(size);
     }
Copy the code

A TaskQueue provides a number of operations to process the queue, especially sorting. This involves a lot of data structure operations, college data structure failure can learn a wave.

The TaskQueue is in principle a balanced binary heap

The root node has the smallest nextExecutionTime, and the children of queue[n] are queue[2n] and queue[2n+1].

Not to mention size(), get(I), and add(TimerTask), getMin() returns the most recent task to execute, which is queue[1].

RemoveMin () is to delete the most recently executed task, and delete is a classic, university data structure must learn the point, the end of the queue to the head, set the end of the queue to null, and reorder.

    void removeMin(a) {
         queue[1] = queue[size];
         queue[size--] = null;  // Drop extra reference to prevent memory leak
         fixDown(1);
     }
Copy the code

The fixDown(int) function filters the queue[k] to its children until the nextExecutionTime is less than or equal to the 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; }}Copy the code

The fixUp(int) function is to overflow, to continuously upgrade, when the TimerTask is added.

     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; }}Copy the code

QuickRemove (int) Removes the specified element. Why is it called quick? It just assigns and doesn’t sort. Sort is done with heapify().

     void quickRemove(int i) {
         assert i <= size;
 ​
         queue[i] = queue[size];
         queue[size--] = null;  // Drop extra ref to prevent memory leak
     }
     
     void heapify(a) {
         // Order the second half of the queue
         for (int i = size/2; i >= 1; i--)
             fixDown(i);
     }
Copy the code

RescheduleMin (newTime) Resets the next execution time of the current task and sorts it. Why queue[1]?

Because in the actual execution, it takes the shortest time for the next execution, namely queue[1].

void rescheduleMin(long newTime) {
         queue[1].nextExecutionTime = newTime;
         fixDown(1);
     }
Copy the code

1.3.2 TimerThread

TimerThread was briefly mentioned in Section 1.1, but it is highlighted here because it is the focus of the Timer 👴👴👴

TimerThread is simple. TimerThread is where the execution is.

The main body of TimerThread is the mainLoop() method.


 class TimerThread extends Thread {
     // Flag bit, used to determine the status in the mainLoop
     boolean newTasksMayBeScheduled = true;
     
     private TaskQueue queue;
     
     TimerThread(TaskQueue queue) {
         this.queue = queue;
     }
     
     public void run(a) {
         try {
             //mainLoop will be implemented in the following sections
             mainLoop();
         } finally {
             // Set the parameter to false and the queue is cleared
             synchronized(queue) {
                 newTasksMayBeScheduled = false;
                 queue.clear();  // Eliminate obsolete references}}}}Copy the code

So how does TimerThread run? Let me break this down for you

 // We start with a new Timer(), set the default thread name and execute thread.start() through the no-parameter constructor of the Timer.
 // The schedule() method of the Timer puts the TimerTask in the queue and sets the delay, period and state.
 Timer timer = new Timer();
 timer.schedule(timerTask, delay, period);
 ​
 public Timer(a) {
     this("Timer-" + serialNumber());
 }
 ​
 public Timer(String name) {
     New TimerThread is passed to the queue. The new TimerThread is passed to the queue
     //private final TimerThread thread = new TimerThread(queue);
     
     thread.setName(name);
     thread.start();
 }
 ​
 // TimerThread has a TaskQueue property and an overloaded constructor that receives the queue
 private TaskQueue queue;
 ​
 TimerThread(TaskQueue queue) {
     this.queue = queue;
 }
 ​
 // Once thread.start() is executed, the TimerThread's run method is executed, which is in the mainLoop() method
 private void mainLoop(a) {
     while (true) {
         try {
             TimerTask task;
             boolean taskFired;
             synchronized(queue) {
                 // Wait for queue to become non-empty
                 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;
                 task = queue.getMin();
                 synchronized(task.lock) {
                     if (task.state == TimerTask.CANCELLED) {
                         queue.removeMin();
                         continue;  // No action required, poll queue again
                     }
                     currentTime = System.currentTimeMillis();
                     executionTime = task.nextExecutionTime;
                     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); }}}if(! taskFired)// Task hasn't yet fired; wait
                     queue.wait(executionTime - currentTime);
             }
             if (taskFired)  // Task fired; run it, holding no locks
                 task.run();
         } catch(InterruptedException e) {
         }
     }
 }
 ​
     If you read the method in general, you will see that it is an infinite loop unless you encounter a break or an exception that cannot be caught.while (queue.isEmpty() && newTasksMayBeScheduled)
                         queue.wait();
 ​
     // The condition to escape the loop is that queue is not empty or newTasksMayBeScheduled is false
     // Wait to wait for a nitify operation to take place on queue elsewhere, which is when cancel is called
     // If the flag bit is false, the loop is broken and the queue is cleared, the loop is broken.public void cancel(a) {
         synchronized(queue) {
             thread.newTasksMayBeScheduled = false;
             queue.clear();
             queue.notify();  // In case queue was already empty.}}// When the Timer attribute threadReaper calls Finalize
     // This threadReaper overwrites the Finalize method, which is called by GCprivate final Object threadReaper = new Object() {
         protected void finalize(a) throws Throwable {
             synchronized(queue) {
                 thread.newTasksMayBeScheduled = false;
                 queue.notify(); // In case queue is empty.}}};// The sched method in the Timer is used when adding to the queue. At this point the queue is not empty and the loop is broken.// The task is cancelled
     if (task.state == TimerTask.CANCELLED) {
         queue.removeMin();
         continue;  // No action required, poll queue again
     }
 ​
     // After that, the current system time and the last estimated execution time are fetched. If the current system time is exceeded, the execution is hurried.
     // However, it is necessary to determine whether the task is repeated before executing it.
     Period = 0; 0 = 1; If not, rescheduleMin is called to set the next execution time and sort it.
 ​
     currentTime = System.currentTimeMillis();
     executionTime = task.nextExecutionTime;
     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); }}If period is negative, the next execution time is the current system time + cycle time. If it is positive, it is the next execution time of the original calculation + cycle time.
     // This is the difference between schedule and scheduleAtFixedRate. Change the positive and negative values of the parameters, just like the other method.// If the current task is not executed, wait for a period of time.
     if(! taskFired)// Task hasn't yet fired; wait
         queue.wait(executionTime - currentTime);
 ​
     // If the time is up, execute
     if (taskFired)  // Task fired; run it, holding no locks
         task.run();
 ​
Copy the code

Ok, so that's the end of the execution process, isn't that simple! 😏 😏 😏

Looking back at section 1.2, is it clear and clear? 👻

1.4 the Timer to summarize

The Timer can be divided into four parts:

  • TimerTask is the specific content of the scheduling task
  • TaskQueue stores timerTasks to be executed. The smaller the subscript, the higher the priority.
  • TimerThread is an extension of Thread. It gets a TimerTask from the TaskQueue with subscript 1 and executes it. It processes the TaskQueue based on whether it is a repeated task.
  • The TimerThread and TaskQueue are in the Timer class.

The important thing to remember is that if you want to use a TimerTask, you must remember to use a try catch. If you encounter an exception that cannot be caught, the Timer will terminate.

Here you can refer to one of the problems in the production process of: blog.verysu.com/article/435