Principles and usage of timer and timertask

Source: Internet
Author: User

In fact, Timer is a scheduler, while timertask is just a class that implements the run method, and the specific timertask needs to be implemented by yourself, for example:

Timer timer = new Timer();timer.schedule(new TimerTask() {public void run() {System.out.println("abc");}}, 200000 , 1000);

One timertask is directly implemented here (of course, You can implement multiple timertasks. Multiple timertasks can be scheduled by one timer and distributed to multiple timers, later, we will talk about the implementation mechanism of timer, that is, the internal scheduling mechanism), and then write the run method, which starts to be executed 20 s later and runs once every second. Of course, you can use a timer object to operate multiple timertasks, in fact, timertask itself is meaningless. It is just an object operated on with the timer set. to implement it, there must be a corresponding run method to be called. It does not even need to implement runnable, because this is often confusing, why? It is also the focus of this article.

 

When talking about the principles of timer, Let's first look at some common methods in Timer:

public void schedule(TimerTask task, long delay)

This method is to schedule a task, after the delay (MS) began to schedule, only once.

public void schedule(TimerTask task, Date time)

It is scheduled once at the specified time.

public void schedule(TimerTask task, long delay, long period)

This method is to schedule a task that starts scheduling after delay (MS), after each scheduling, at least wait for period (MS) to start scheduling.

public void schedule(TimerTask task, Date firstTime, long period)

Similar to the previous method, the only difference is that the second parameter passed in is the time of the first scheduling.

public void scheduleAtFixedRate(TimerTask task, long delay, long period)

Schedule a task, start scheduling after delay (MS), and then re-schedule each time after period (MS,It seems like the method: schedule is the same, but it is not actuallyLater, you will see from the source code that schedule uses the current time (obtained before the task is executed) + time slice when calculating the next execution time.ScheduleatfixedrateThe method is to use the current execution time (that is, the calculation time appears in the execution time) + time slice. The former is the actual running time, and the latter is the theoretical time point. For example:ScheduleThe time slice is 5 S, So theoretically5, 10, 15, 20These time slices are scheduled, but if they are not scheduled due to some CPU requisition, if they are not scheduled for the first time until the 8sScheduleThe next time calculated by the method should be 13s instead of 10th s, so it is possible that the next time will be 20 s laterLess scheduled once or multiple times, AndScheduleatfixedrateThe method is to theoretically calculate the next scheduling time for sorting. If the 8s is scheduled, the calculation should be 10th s, so it is 2 s from the current time, in the sorting of the scheduling queue, the scheduling is prioritized.Minimize missed Scheduling.

public void scheduleAtFixedRate(TimerTask task, Date firstTime,long period)

The method is the same as above. The only difference is that the first scheduling time is set to a date time, instead of a time slice of the current time. We will describe these content in the source code.

Next, let's look at the source code.

First, we can see several timer constructor methods:

 

Constructor 1: No parameter constructor. A thread name is constructed simply by using tiemer as the prefix:

public Timer() {    this("Timer-" + serialNumber());}

Whether the input is a background thread. If it is set to a background thread, the timer automatically ends after the main thread ends, instead of using cancel to end the timer.

Constructor 2: indicates whether the thread is a background thread. The background thread is automatically deregistered only when the process ends.

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

The other two constructor methods are used to input names and start Timer:

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

Here is a thread, which is obviously a thread and encapsulated in the Timer class. We can see that the definition of this thread is:

private TimerThread thread = new TimerThread(queue);

What defines timerthread is:

class TimerThread extends Thread {

As you can see, Timer encapsulates a thread internally for scheduling independent from external threads, while timerthread is of the default type and cannot be referenced by default, it is used by the timer.

 

Next, let's take a look at the attributes.

In addition to the thread mentioned above, there is also a very important attribute:

private TaskQueue queue = new TaskQueue();

If you look at the name, you will know that it is a queue. You can guess what it is in the queue. It should probably be the task I want to schedule. Record it first, and continue to look at it below:

There is also a property: threadreaper, which is of the object type. It only overwrites the Finalize method to recycle the corresponding information and perform garbage collection, that is, when the timer thread dies for some reason and is not cancel, the information in the queue needs to be cleared, but we usually do not consider this method, so you just need to know what Java is doing to write this method.

Next, let's look at the implementation of the scheduling method:

For the above six scheduling methods, we will not list them one by one. Why do you know it when you wait:

Let's look at the following method:

public void schedule(TimerTask task, long delay)

The source code is as follows:

    public void schedule(TimerTask task, long delay) {        if (delay < 0)            throw new IllegalArgumentException("Negative delay.");        sched(task, System.currentTimeMillis()+delay, 0);    }

Another method is called here to pass in the task. The first parameter is passed in to system. currenttimemillis () + delay can be seen as the time point of the first execution time (if date is input, it is an object. gettime () is enough, so you don't need to talk about the several methods to pass in date). The third parameter is 0. Here we can guess whether it is a time slice or a number of times, but we will know what it is. In addition, we don't have to worry about the method: sched content. Let's first look at how the overloaded method works.

Let's look at the method:

Public
Void
Schedule (timertask task,LongDelay,LongPeriod)

Source code:

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

It seems that sched is also called to complete the scheduling. The difference between sched and sched is that the input period is added, and the first input is 0, therefore, make sure that this parameter is a time slice, instead of a number of times. Note that the period in this parameter adds a negative number, that is, the inverse number, that is, we start to pass 1000, when sched is called, it will become-1000. In fact, after reading the source code, you will find that this is a foreigner's understanding of a number, and it does not have any special meaning, so there are also these difficulties when reading the source code.

The method is as follows:

Public
Void
Scheduleatfixedrate (timertasktask,LongDelay,LongPeriod)

Source code:

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

The only difference is that there is no inverse in period. In fact, you have finally read the source code. The inverse mentioned above has no special meaning. foreigners do not want to add a parameter to indicate scheduleatfixedrate, scheduleatfixedrate is the same as most of schedule's logic code. Therefore, the parameter range is used as the differentiation method, that is, when the parameter you pass in is not a positive number, you call the schedule method to obtain the scheduleatfixedrate function, and call the scheduleatfixedrate method to obtain the function of the schedule method:

 

Let's look at the implementation body of the sched method:

     private void sched(TimerTask task, long time, long period) {        if (time < 0)            throw new IllegalArgumentException("Illegal execution time.");        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);            if (queue.getMin() == task)                queue.notify();        }    }

Queue is a queue. First, we don't look at its data structure. We can see that the synchronization occurred when he was doing this operation. Therefore, at the timer level, this is thread-safe, finally, assign values to task-related parameters, includingNextexecutiontime(Next execution time), period (time slice), State (Status), and then put it into the queue for a notify Operation. Why do I need to perform the notify Operation? After reading the code, you will know.

 

In short, here is the process of putting tasks into the queue. At this time, you may be interested in the queue structure. Let's take a look at the structure of the queue attribute taskqueue:

class TaskQueue {    private TimerTask[] queue = new TimerTask[128];    private int size = 0;

 

It can be seen that the structure of taskqueue is very simple. Adding a size for an array is a bit like arraylist. Is it 128 in length? Of course not. arraylist can be expanded. It can, it only causes memory copying. Therefore, if the number of internal tasks does not exceed 128, A timer will not cause resizing. Add (timertask) and size () are provided internally (), getmin (), get (INT), removemin (), quickremove (INT), reschedulemin (long newtime), isempty (), clear (), fixup (), fixdown (), heapify ();

The following method indicates:

Add (timertaskt)To add a task

Size ()Task queue length

Getmin ()Obtain the latest task to be executed after the current sorting. The subscript is 1, and the queue header 0 is not operated.

Get (INTI)Obtain the data of the specified subobject, including subscript 0.

Removemin ()To delete the most recently executed task, that is, the first element, timertask can be removed from the queue by calling this method after only one scheduled task is executed.

Quickrmove (INTI)Generally, this method is not called to delete the specified element. This method is used only when the purge occurs in timer andTimertaskCalledCancelThis method is called, that is, to cancelTimertaskAnd then it will be removed from the queue (Note that if the task is being executed or is still in progress, although it is removed from the queue ), in addition, this cancel method is not the Cancel Method of timer but the timertask method. One is the scheduler and the other is a single task. Finally, note that after this quickrmove is completed, is to add the last element of the queue to this position, so this will cause the order inconsistency problem, there will be a method to perform the replenishment later.

Reschedulemin(Long newtime) is to reset the next execution time of the current task, and sort it in the queue to the appropriate position, and callFixdownMethod.

ForFixupAndFixdownIn terms of method, the former is when a new task is added, first put the elements at the end of the queue, and then look forward for whether there are tasks that are executed later than themselves. If yes, exchange the order of the two tasks. Fixdown is the opposite. After the first task is executed, you need to add a time slice to get the next execution time. Therefore, you need to compare the sequence with the subsequent tasks.

Next, let's take a look.FixdownThe details are:

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

This method is not sort, but finds a suitable position for exchange, because it is not to find one by one through the queue, but to move a binary value every time, for example, when 1 is passed in, the next step is the locations 2, 4, 8, and 16. You just need to find the proper location and put it down. The order may not be completely ordered, it only needs to see that the closer it is to the scheduling part, the more orderly it is, so that it can ensure a certain order and achieve better performance.

 

The last method isHeapifyIn fact, the last half of the queue is all done once.FixedownThis operation is mainly forQuickremoveMethod. After a large number of quickrmove operations are performed, the half of the rows are sorted in a very simple order.

 

We don't talk about the source code for these methods. We only need to know that it provides something similar to arraylist for management. There are a lot of internal sorting and other processing. We continue to return to timer, and there are two other methods in it: the cancel () and method purge () methods, in fact, in terms of the cancel method, a cancellation operation, in the test, you will find that once this method is executed, timer will end, let's take a look at the source code:

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

It seems that the queue is cleared, and the newtasksmaybescheduled status is set to false. In the end, the queue also calls the notify operation, but there is nothing to end the thread, then we will return to the Thread class timerthread we started to talk about. Before looking at this class, let's look at the last Purge () class in timer, after cancel operations are performed on many tasks, you can call the purge method to reclaim the class spaces that cancel has dropped. As mentioned above, this will cause disordered order, therefore, you need to call the heapify method in the team to rearrange the sequence. The source code is as follows:

    public int purge() {         int result = 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;     }

So how is scheduling done? How are those running y and clearing queues done? Let's take a look at the timerthread class. There is an internal attribute: newtasksmaybescheduled, that is, the parameter we mentioned at the beginning will be set to false during cancel.

Another property defines

PrivateTaskqueue queue;

That is, the queue we have called. It is connected now, but here the queue is passed through the constructor method, and then assigned a value for the operation, it is obvious that timer is passed to this thread. We know it is a thread, so the execution center is naturally the run method. So let's take a look at the body part of the run method:

    public void run() {        try {            mainLoop();        } finally {            synchronized(queue) {                newTasksMayBeScheduled = false;                queue.clear();  // Eliminate obsolete references            }        }    }

Try is very simple. It's just a mainloop. it's known that the name is the main loop program. In Finally, it is also an inevitable program to set the parameter to false and clear the queue.

So the core thing is mainloop. Yes, I understand all about mainloop:

    private void mainLoop() {        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) {            }        }    }

It can be found that this timer is an endless loop program. It jumps out unless exceptions that cannot be captured or break occurs. Pay attention to this Code:

While(Queue. isempty () & newtasksmaybescheduled)

Queue. Wait ();

When the loop body is in the loop process, the condition is that the queue is empty and the newtasksmaybescheduled status is true, you can see the key role of this status, that is, the condition for jumping out of the loop is that the queue is not empty, either the newtasksmaybescheduled status is set to false to jump out, And wait is waiting for other places to perform the notify Operation on the queue, as shown in the code above, when the ADD and cancel Methods occur and threadreaper calls the Finalize method, it will be called. The third one is basically not considered when the add method occurs, that is, when the queue is still empty, when "add" occurs, the queue will jump out of the loop if it is not empty, while "cancel" sets the status. Otherwise, it will not enter this loop. The following code is used:

If(Queue. isempty ())

Break;

After jumping out of the above loop, if newtasksmaybescheduled is set to false, that is, cancel is called, then the queue is empty, and the external endless loop is directly exceeded, therefore, cancel is implemented in this way. If the following tasks are not running yet, cancel does not work.

 

The next step is to obtain the current system time and the last estimated execution time. If the expected execution time is earlier than the current system time, it needs to be executed. At this time, determine whether the time slice is 0. If it is 0, call the removemin method to remove the task. Otherwise, use reschedulemin to set the latest time and sort the tasks:

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

Here we can see that when the period is negative, it will be considered to calculate the next time according to the current system time + A time slice, that is, the difference between schedule and scheduleatfixedrate mentioned above, in fact, it is determined by positive and negative numbers. Maybe JAVA does not want to add parameters, but also increases the readability of the program. In fact, it is somewhat strange to use positive and negative judgments, that is, if you pass in a negative number in the schedule method, the function is the same as that of scheduleatfixedrate. On the contrary, the negative number function and schedule method are the same in the scheduleatfixedrate method.

At the same time, you can see that the period is 0, that is, only one execution, so the time slice is used for plus or minus 0, huh, and then look at the next part of mainloop:

If(! Taskfired) // taskhasn' t yet fired; wait

Queue. Wait (executiontime-currenttime );

If the task execution time is not yet reached, wait for a while. Of course, this wait is likely to be awakened when other threads operate add and cancel, because there is a dead y method internally, therefore, this time is not completely accurate. In most cases, the task information in timer is stable, and the cancel method is used to wake up the task.

 

Finally:

If(Taskfired) // task fired; run it, holding no locks

Task. Run ();

If the thread needs to be executed, call its run method instead of starting a new thread or obtaining a thread from the thread pool for execution, therefore, the run method of timertask is not a multi-thread run method. Although runnable is implemented, it is only used to indicate that it is executable and does not mean that it must be executed through a thread.

 

Let's look at it again:

TimerAndTimertaskIs a simple combination of multiple threads? No, a timer internally Wraps"One thread"And"One task"Queue. This queue queues tasks in a certain way, including threads inTimerWhen the constructor is called, the thread's run method infinite loop this task queue, if the queue is empty and does not occurCancelOperation. If the queue is still empty after the wait is completed, cancel is deemed to have occurred and the task is terminated; if a task is found to be executed less than the system time in a loop, the task needs to be executed. The next execution time is calculated based on the time slice of the task. If the time slice is 0, the task is executed only once, remove the queue directly.

But can I implement multiple threads? Yes. Whether or not everything is multi-threaded depends on your own wishes. Multiple timers are multithreading, and each timer has its own thread processing logic, of course, timer is not very suitable for fast scheduling of many tasks in a short period of time. At least it is not very suitable for hanging many tasks on the same timer, in the field of multithreading, we use multiple threads:

Executors.Newscheduledthreadpool

To process the thread pool in the scheduling queue.NewScheduledthreadpoolexecutorTo create a thread poolExecutorOf course, you can also call:

Executors.Unmarshablescheduledexecutorservice

Method To createDelegatedscheduledexecutorserviceIn fact, this class is packaged belowScheduleexecutorThat is, it is just a shell. In English, it means the meaning of being delegated and managed.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.