Java Concurrency Programming: Timer and TimerTask (reprint)
The following content is reproduced from:
http://blog.csdn.net/xieyuooo/article/details/8607220
In fact, the timer is a scheduler, and TimerTask is just a implementation of the run method of a class, and the specific timertask need to be implemented by yourself, such as:
Timer timer = new timer (); Timer.schedule (new TimerTask () {public void run () {SYSTEM.OUT.PRINTLN ("abc");}}, 200000, 1000);
Here directly implement a timertask (of course, you can implement multiple timertask, multiple timertask can be dispatched by a timer will be assigned to a number of timer, the following will say that the implementation mechanism of the timer is the internal scheduling mechanism), Then write the Run method, after 20s execution, execute once per second, of course, you through a timer object to manipulate multiple timertask, in fact, timertask itself is meaningless, just and timer set operation of an object, implement it must have corresponding to the Run method, In order to be called, he doesn't even need to implement runnable, because this often confuse, why? is also the focus of this article.
When it comes to the principle of the timer, let's look at some common methods in the timer:
public void Schedule (timertask task, long delay)
The method is to dispatch a task, after delay (MS), to start scheduling, only dispatched once.
public void Schedule (timertask task, Date time)
Dispatched once at a specified point in time.
public void Schedule (timertask task, long delay, long period)
This method is to dispatch a task, after delay (MS) to start the dispatch, after each dispatch, the minimum waiting 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 dispatch.
public void Scheduleatfixedrate (timertask task, long delay, long period)
Scheduling a task, after delay (ms) to start scheduling, and then every period (ms) again dispatched, seemingly and method: schedule is the same, in fact , you will see according to the source code, Schedule calculates the time of the next execution by the current time (which is obtained before the task is executed) + the time slice, and the scheduleatfixedrate method through the time currently required to execute (that is, to calculate the time that should be executed now) + time slice, The former is the actual time of the operation, and the latter is a theoretical time point, for example:schedule time Slice is 5s, then theoretically in 5, 10, 15, These time slices are scheduled, but if due to some CPU requisition caused by not scheduled, If you wait until the first time, the schedule method calculates the next time should be 13s instead of 10s, it is possible to next to 20s and be less dispatched once or many times , and Scheduleatfixedrate method is each time the theory calculates the next need to schedule the time to sort, if the 8s is dispatched, then the calculation should be 10s, so it is 2s from the current time, then scheduling queue sequencing, will be prioritized scheduling, then minimize the problem of missing schedules.
public void Scheduleatfixedrate (timertask task, Date Firsttime,long period)
In the same way, the only difference is that the first scheduling time is set to a date time, not a time slice of the current time, which we will describe in detail in the source code.
Next look at the source
First, there are several ways to construct a timer:
Construction Method 1: No parameter construction method, simple to construct a thread name by Tiemer prefix:
Public Timer () {This ("timer-" + serialnumber ());}
The thread that is created is not the main course, and the timer ends automatically after the main thread ends without using Cancel to complete the timer.
Construction Method 2: passed in as a background thread, the background thread is automatically logged off when and only if the process ends.
Public Timer (Boolean Isdaemon) {This ("timer-" + serialnumber (), Isdaemon);}
The other two construction methods are responsible for passing in names and starting the timer:
Public Timer (String name, Boolean Isdaemon) { thread.setname (name); Thread.setdaemon (Isdaemon); Thread.Start (); }
Here's a thread, which is obviously a thread that is wrapped in a timer class, and we look at the definition of this thread:
Private TimerThread thread = new TimerThread (queue);
And the definition of the TimerThread section is:
Class TimerThread extends Thread {
See here that the timer inside wraps a thread that is used to make a schedule independent of the external thread, while TimerThread is a default type, which is not referenced by the defaults and is used by the timer itself.
And then look at the attributes.
In addition to the thread mentioned above, there is a very important property:
Private Taskqueue queue = new Taskqueue ();
Look at the name to know is a queue, the queue inside can guess first guess what is, then probably should be I want to dispatch the task, first recorded, then continue to look down:
There is also a property is:Threadreaper, it is the type of object, just rewrite the Finalize method, is for garbage collection, the corresponding information is recycled, to do GC callback, that is, when the timer thread died for some reason, Instead of being cancel, the information inside the queue needs to be emptied out, but we usually don't think about it, so we know what Java is doing with this method.
Next look at the implementation of the scheduling method:
For the above 6 scheduling methods, we do not enumerate each, why wait for you to know:
Look at the 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); }
Here another method is called, the task is passed in, and the first argument passed in to System.currenttimemillis () +delay is visible as the point in time for the first time to execute (if the date is passed in, it is the object. GetTime (), So the introduction of several methods of the date is not to say more, and the third parameter passed 0, here can be guessed either the time slice, or the number of what, but will know what it is; Also on the method: Sched content We don't have to go to see him, first look at how the overloaded method is done
Then look at the method:
public void Schedule (TimerTask task, long Delay,long period)
The source code is:
public void Schedule (timertask task, long delay, long period) { if (Delay < 0) throw new Illegalargumentexcepti On ("negative delay."); if (period <= 0) throw new IllegalArgumentException ("non-positive period."); Sched (Task, System.currenttimemillis () +delay,-period); }
It seems also called the method sched to complete the schedule, and the method is the only time the difference is to increase the incoming period, and the first incoming is 0, so determine this parameter is the time slice, not the number of times, note that the period in this added a negative, that is, take the reverse, That is, we start to pass 1000, when the call Sched will become-1000, in fact, after reading the source you will find that this is a foreigner for a number of understanding, and not have any special meaning, so read the source of the time also have these difficulties.
The last way to look at this is:
public void Scheduleatfixedrate (Timertasktask,long delay,long period)
The source code is:
public void Scheduleatfixedrate (timertask task, long delay, long period) { if (Delay < 0) throw new Illegalargu Mentexception ("negative delay."); if (period <= 0) throw new IllegalArgumentException ("non-positive period."); Sched (Task, System.currenttimemillis () +delay, period); }
The only difference is that in the period did not take the opposite, in fact, you finally read the source, the above is not any special meaning, foreigners do not want to add a parameter to represent Scheduleatfixedrate, Most of the logic code in Scheduleatfixedrate and schedule is consistent, so the range of parameters is used as a distinguishing method, that is, when you pass in a parameter that is not a positive number, You call the schedule method is just to get the function of Scheduleatfixedrate, and call Scheduleatfixedrate method is exactly the function of schedule method, hehe, these discussions are meaningless, discuss the essence and focus:
Look at the implementation 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 Illegalstateexce Ption ("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 for a queues, we do not look at his data structure, see him in this operation, the synchronization occurred, so at the timer level, this is thread-safe, and finally the task-related parameters assigned value, mainly including Nextexecutiontime (Next execution time), period (time slice), state, then put it into the queue, do a notify operation, why do notify operation? Look at the code behind you and you'll know.
In a nutshell, here is the process of putting a task into a queue, at which point you might be interested in the structure of the queues, so let's take a look at the structure of the queue property Taskqueue:
Class Taskqueue { private timertask[] queue = new timertask[128]; private int size = 0;
Visible, Taskqueue structure is very simple, for an array, plus a size, a bit like ArrayList, is not the length of 128, of course, is not, ArrayList can be expanded, it can, just will cause memory copy, so a timer, As long as the internal task number of not more than 128 is not caused by the expansion of the internal supply of Add (timertask), size (), getmin (), get (int), removemin (), quickremove (int), Reschedulemin (Long NewTime), IsEmpty (), clear (), fixup (), Fixdown (), heapify ();
The method in this way probably means:
Add (Timertaskt) to add a task
size () The length of the task queue
getmin () Gets the most recent task that needs to be performed after the current sort, with the subscript 1, and the queue header 0 without any action.
Get (inti) gets the data for the specified subscript, including subscript 0, of course.
removemin () removes the current most recently executed task, which is the first element, usually a task that is scheduled only once, and, after execution, calls this method to remove the TimerTask from the queue.
Quickrmove (inti) deletes the specified element, in general, does not call this method, this method only when the timer occurs purge, and when the corresponding TimerTask calls the cancel method, the method is called, which cancels a timertaskand then is removed from the queue (note if the task is in execution, still in execution, although it is removed in the queue), And this is the Cancel method is not the timer's Cancel method but TimerTask, one is the scheduler, one is a single task, and finally note that this quickrmove is completed, is to add the last element of the queue to this location, Therefore, there will be an inconsistent sequence of problems, there will be a method for the back-up.
Reschedulemin (Long NewTime) is to reset the next execution time of the currently executing task and to sort it from the new to the appropriate position in the queue, while calling the Fixdown method that is said later.
For the fixup and Fixdown methods, the former is when a new task is added, first put the element at the end of the queue, and then forward to see if there is a task later than their own, if there is, the order of two tasks to exchange a bit. Instead of Fixdown, after the first task, you need to add a time slice to get the next execution time, so you need to compare the order to the task that follows.
Second, you can look at the details of the Fixdown :
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 is not a sort of order, but to find a suitable location to exchange, because not through the queue to find, but each move a binary, for example, when passing 1, the next is 2, 4, 8, 16 These positions, find the right place to put down, the order may not be completely orderly, It only needs to see the distance dispatch part of the closer is the order of the stronger the time can be, so that can guarantee a certain order, to achieve better performance.
The last method is heapify, in fact, will be the end of the queue, all do a fixedown operation, the operation is mainly to Quickremove method of back-up, When a large number of quickrmove, the order is disrupted, at this time half of the area to do a very simple sort.
These methods we do not say the source code, just need to know that it provides similar to the ArrayList of things to manage, there are many sort of internal processing, we continue to go back to the timer, there are two methods are: Cancel () and Method Purge () method, in fact, in terms of the Cancel method , a cancel operation, in the test you will find, if once executed this method the timer will end, look at the source code is what:
public void Cancel () { synchronized (queue) { thread.newtasksmaybescheduled = false; Queue.clear (); Queue.notify (); In case the queue was already empty. } }
It seems that just empty the queue, then set the newtasksmaybescheduled state to false, and finally let the queue call the next notify operation, but there is no place for the thread to end, Then go back to what we started talking about. The timer contains the thread: TimerThread class, before looking at this class, look at the last Purge () class in the timer, when you do a lot of tasks cancel operation, At this time by calling the Purge method to realize the recovery of these cancel class space, as mentioned above, this will cause the order confusion, it is necessary to call the team's Heapify method to complete the sequential rearrangement, 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 dispatch, how is the dispatch, those notify, and empty queue is how to do it? We're going to look at the TimerThread class, and there's a property inside that is: newtasksmaybescheduled, which is the argument we started with, which is set to False at cancel.
Another property defines the
Private Taskqueue queue;
That is, we call the queue, this is connected to it, but here is the queue is passed through the construction method, passed the assignment to operate, it is obvious that the timer passed to the thread, we know it is a thread, so the execution of the center is naturally the Run method, So 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, just a mainloop, look at the name know is the main loop program, finally is the inevitable program to execute the parameter is False, and the queue is emptied out.
Then the core is the Mainloop, yes, read Mainloop all understand:
private void Mainloop () {while (true) {try {TimerTask task; Boolean taskfired; Synchronized (queue) {//Wait for the queue to become non-empty while (Queue.isempty () & amp;& newtasksmaybescheduled) queue.wait (); if (Queue.isempty ()) break; Queue is empty and would forever remain; Die//Queue nonempty; Look at first evt and does 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.currenttimemi Llis (); ExecutionTime = Task.nexteXecutiontime; if (taskfired = (executiontime<=currenttime)) {if (Task.period = = 0) {//non-repeating, RE Move 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 a dead loop program, unless you encounter an uncaught exception or break to jump out, first notice this code:
while (Queue.isempty () &&newtasksmaybescheduled) queue.wait ();
Loop body is the loop process, the condition is that the queue is empty and the newtasksmaybescheduled state is true, you can see the state of its key role, that is, the condition of jumping out of the loop is either the queue is not empty, or is The newtasksmaybescheduled state is set to false to jump out, and wait is waiting for the notify operation to occur elsewhere on the queue, which can be found in the code above, when the Add, Cancel and will be called when Threadreaper calls the Finalize method, and the third we can basically not consider when the add is actually occurred when the queue or empty time, the occurrence of add so that the queue is not empty to jump out of the loop, and cancel is set the state, Otherwise it won't go into this loop, so look at the following code:
if (Queue.isempty ()) break ;
When jumping out of the above loop, if the newtasksmaybescheduled state is set to false jump, that is, the call to cancel, then the queue is empty, this time directly out of the outside of the dead loop, so cancel is the implementation of this, Cancel does not work if the following tasks are still running and not running here.
The next step is to get a current system time and the last expected execution time, if the expected execution time is less than the current system time, then need to execute, at this time to determine whether the chip is 0, if 0, call the Removemin method to remove it, Otherwise, set the task through Reschedulemin to the latest time and sort:
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 can be seen, when the period is negative, it will be considered in accordance with the current system time + a time slice to calculate the next time, that is, the difference between the schedule and scheduleatfixedrate, in fact, the interior is determined by positive negative numbers, Perhaps Java does not want to add parameters, but also want to increase the readability of the program, in fact, through the positive and negative judgment is somewhat strange, that is, if you are in the schedule method to reach the function of negative and scheduleatfixedrate function is the same, Conversely, the Scheduleatfixedrate method is the same as the schedule method for passing in the negative number function.
At the same time you can see period is 0, is only executed once, so the time slice plus and minus 0 are used, hehe, and then look at the next part of Mainloop:
if (!taskfired)//TASKHASN ' t yet fired; Wait queue.wait (executiontime-currenttime);
Here is if the task execution time is not yet, wait for a period of time, of course, this wait is likely to be the other thread operation add and cancel when the time is awakened, because there is notify method inside, so this time is not completely accurate, In most cases it is considered that the task information inside the timer is stable, and the Cancel method wakes up is another matter.
At last:
if (taskfired)//Task fired; Run it, holding no locks task.run ();
If the thread needs to execute, then call its run method instead of starting a new thread or getting a thread from the thread pool to execute, so TimerTask's Run method is not a multithreaded run method, although it implements Runnable, but only to indicate that it is executable, Does not mean that it has to be executed in a thread way.
Look back and see:
Is the simple combination of Timer and timertask multi-threaded? No, a timer internally wraps the " one thread" and the " one task" queue, which queues the task in a certain way, and the included thread is started when the timer 's constructor method is called. The thread's Run method loops the task queue indefinitely, and if the queue is empty and the cancel operation does not occur, it waits until the queue is empty after the wait is completed, and the cancel is considered to have occurred and the end of the task In the loop, if the task needs to be executed less than the time of the system, it needs to be executed, then the next execution time is calculated from the time slice of the task, and if the time slice is 0 for the execution only once, the queue can be removed directly.
But is it possible to implement multithreading? Yes, anything is multi-threaded completely look at the personal will, more than one timer is multi-threaded, each timer has its own threading logic, of course, the timer from here is not very suitable for many tasks in a short period of rapid scheduling, at least not very suitable for the same timer hanging a lot of tasks , in the multi-threaded domain We are more using multiple threads:
Executors.newscheduledthreadpool
To complete the processing of the thread pool in the dispatch queue, create the Executor of the thread pool internally through the new Scheduledthreadpoolexecutor , and of course it can be called:
Executors.unconfigurablescheduledexecutorservice
method to create a delegatedscheduledexecutorservice in fact, this class is wrapped down scheduleexecutor, that is, this is just a shell, English understanding is delegated meaning, Meant to be managed.
Specific examples of use can refer to this blog post:
Http://www.bdqn.cn/news/201305/9303.shtml
Java Concurrency Programming: Timer and TimerTask (reprint)