[high concurrency] deeply analyze the source code of ScheduledThreadPoolExecutor class

original text    https://xie.infoq.cn/article/79486af98d788161e46e3ca9b

In the column [high concurrency topics], we deeply analyzed the source code of ThreadPoolExecutor class, and ScheduledThreadPoolExecutor class is a subclass of ThreadPoolExecutor class. Today, let's tear up the source code of the ScheduledThreadPoolExecutor class.

Construction method

Let's first look at the construction method of ScheduledThreadPoolExecutor. The source code is as follows.

public ScheduledThreadPoolExecutor(int corePoolSize) {  super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());}
public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) {  super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,      new DelayedWorkQueue(), threadFactory);}
public ScheduledThreadPoolExecutor(int corePoolSize, RejectedExecutionHandler handler) {  super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,      new DelayedWorkQueue(), handler);}
public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory, RejectedExecutionHandler handler) {  super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,      new DelayedWorkQueue(), threadFactory, handler);}

From the perspective of code structure, ScheduledThreadPoolExecutor class is a subclass of ThreadPoolExecutor class, and the construction method of ScheduledThreadPoolExecutor class actually calls the construction method of ThreadPoolExecutor class.

schedule method

Next, let's take a look at the schedule method of the ScheduledThreadPoolExecutor class. The source code is as follows.

public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {  //If the passed Runnable object and TimeUnit time unit are null / / a null pointer exception is thrown if (command = = null | unit = = null) throw new nullpointerexception()// Encapsulate the task object and directly return the scheduledfuturetask object runnablescheduledfuture <? > in the decoratetask method t = decorateTask(command, new ScheduledFutureTask<Void>(command, null, triggerTime(delay, unit)));  // Execute delayed task delayedExecute(t)// Return to task return t;}
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)   //If the passed Callable object and TimeUnit time unit are null / / a null pointer exception is thrown if (label = = null | unit = = null) throw new nullpointerexception()// Encapsulate the task object and directly return the ScheduledFutureTask object in the decorateTask method. Runnablescheduledfuture < V > t = decorateTask (label, new ScheduledFutureTask < V > (label, trigger time (delay, unit)))// Execute delayed task delayedExecute(t)// Return to task return t;}

As can be seen from the source code, the ScheduledThreadPoolExecutor class provides two overloaded schedule methods, and the first parameter of the two schedule methods is different. You can pass either a Runnable interface object or a Callable interface object. Inside the method, the Runnable interface object and Callable interface object will be encapsulated into a RunnableScheduledFuture object, which is essentially encapsulated into a ScheduledFutureTask object. And execute the delayed task through the delayedExecute method.

In the source code, we see that both schedule s call the decorateTask method. Next, let's take a look at the decorateTask method.

decorateTask method

The source code of decorateTask method is as follows.

protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) {  return task;}
protected <V> RunnableScheduledFuture<V> decorateTask(Callable<V> callable, RunnableScheduledFuture<V> task) {  return task;}

From the source code, we can see that the implementation of the decorateTask method is relatively simple. It receives a Runnable interface object or Callable interface object and the encapsulated RunnableScheduledFuture task. Both methods return the RunnableScheduledFuture task directly. These two methods can be overridden in subclasses of the ScheduledThreadPoolExecutor class.

Next, let's continue to look at the scheduleAtFixedRate method.

scheduleAtFixedRate method

The source code of scheduleAtFixedRate method is as follows.

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {  //If the passed in Runnable object and TimeUnit are null, a null pointer exception if (command = = null | unit = = null) throw new nullpointerexception()// If the value passed in by the execution cycle period is less than or equal to 0 / / an illegal parameter exception is thrown if (period < = 0) throw new illegalargumentexception()// Encapsulate the Runnable object into a ScheduledFutureTask, / / and set the execution cycle ScheduledFutureTask < void > SFT = new ScheduledFutureTask < void > (command, null, trigger time (initialdelay, unit), unit. Tonanos (period))// Calling the decorateTask method essentially returns the ScheduledFutureTask object runnablescheduledfuture < void > t = decorateTask (command, SFT)// Set the executed task sft.outerTask = t// Execute delayed task delayedExecute(t)// Return the executed task return t;}

It can be seen from the source code that the scheduleAtFixedRate method encapsulates the passed Runnable object into a ScheduledFutureTask task object and sets the execution cycle. Compared with the previous execution time, the next execution time adds the period duration, and the specific unit of the duration is determined by TimeUnit. A fixed frequency is used to perform timed tasks.

Another method for scheduling tasks in the ScheduledThreadPoolExecutor class is the scheduleWithFixedDelay method. Next, let's take a look at the scheduleWithFixedDelay method.

scheduleWithFixedDelay method

The source code of the scheduleWithFixedDelay method is as follows.

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {  //If the passed in Runnable object and TimeUnit are null, a null pointer exception if (command = = null | unit = = null) throw new nullpointerexception()// If the task delay time is less than or equal to 0, an illegal parameter exception if (delay < = 0) throw new illegalargumentexception()// Encapsulate the Runnable object into a ScheduledFutureTask / / and set a fixed execution cycle to execute the task ScheduledFutureTask < void > SFT = new ScheduledFutureTask < void > (command, null, trigger time (initialdelay, unit), unit.tonanos (- delay))// Call the decorateTask method, which essentially returns the ScheduledFutureTask directly. Runnablescheduledfuture < void > t = decorateTask (command, SFT)// Set the executed task sft.outerTask = t// Execute delayed task delayedExecute(t)// Return to task return t;}

From the source code of the scheduleWithFixedDelay method, we can see that the execution cycle is set when encapsulating the Runnable object into ScheduledFutureTask, but the execution cycle set at this time is different from that set by the scheduleAtFixedRate method. The execution cycle rule set at this time is: the execution time of the next task is the completion time of the previous task plus the delay duration. The duration unit is determined by TimeUnit. In other words, the specific execution time is not fixed, but the execution cycle is fixed, and the whole adopts a relatively fixed delay to execute timed tasks.

If you are careful, you will find that when you set the execution cycle in the scheduleWithFixedDelay method, the passed delay value is negative, as shown below.

ScheduledFutureTask<Void> sft =    new ScheduledFutureTask<Void>(command, null, triggerTime(initialDelay, unit), unit.toNanos(-delay));

The negative number here represents a relatively fixed delay.

In the ScheduledFutureTask class, there is a setNextRunTime method. This method will be called after the run method has finished the task. This method can better reflect the difference between the scheduleAtFixedRate method and the scheduleWithFixedDelay method. The source of the setNextRunTime method is shown below.

private void setNextRunTime() {  //Length of time from next task execution long p = period// Execute the task at a fixed frequency. / / the last time the task was executed. / / add the execution cycle of the task if (P > 0) time + = p// Relatively fixed delay / / the current system time is used / / plus the task execution cycle else time = triggertime (- P);}

In the setNextRunTime method, determine whether the task is executed at a fixed frequency or with a relatively fixed delay by judging the duration of the next task execution.

Trigger time method

Two triggerTime methods are provided in the ScheduledThreadPoolExecutor class to obtain the specific time of the next task execution. The source code of the triggerTime method is as follows.

private long triggerTime(long delay, TimeUnit unit) {  return triggerTime(unit.toNanos((delay < 0) ? 0 : delay));}
long triggerTime(long delay) {  return now() +    ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));}

The code of the two triggerTime methods is relatively simple, that is, to obtain the specific time of the next task execution. It should be noted that: delay < (long. Max_value > > 1 judge whether the value of delay is less than half of Long.MAX_VALUE. If it is less than half of Long.MAX_VALUE, it will directly return delay. Otherwise, it is necessary to deal with overflow.

We see that the overflow prevention logic in the triggerTime method uses the overflow free method. Next, let's look at the implementation of the overflow free method.

Overflow free method

The source code of the overflow free method is as follows.

private long overflowFree(long delay) {  //Obtain the node in the queue. Delayed head = (Delayed) super.getQueue().peek(); / / if the obtained node is not empty, perform subsequent processing if (head! = null) {/ / obtain the delay time from the queue node. long headDelay = head.getDelay(NANOSECONDS) ; / / if the delay time obtained from the queue is less than 0, and the delay / / value passed minus the delay time obtained from the queue node is less than 0 if (headdelay < 0 & & (delay - headdelay < 0)) / / set the value of delay to long.max_value + headdelay = long.max_value + headdelay;} / / return delay;}

Through the source code analysis of the overflow free method, we can see that the essence of the overflow free method is to limit the delay time of all nodes in the queue to be within the Long.MAX_VALUE value and prevent overflow in the compareTo method in the ScheduledFutureTask class.

The source code of the compareTo method in the ScheduledFutureTask class is as follows.

public int compareTo(Delayed other) {  if (other == this) // compare zero if same object    return 0;  if (other instanceof ScheduledFutureTask) {    ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;    long diff = time - x.time;    if (diff < 0)      return -1;    else if (diff > 0)      return 1;    else if (sequenceNumber < x.sequenceNumber)      return -1;    else      return 1;  }  long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);  return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;}

The main function of compareTo method is to sort the delayed tasks, and the tasks ahead of the next execution time are ranked first.

delayedExecute method

The delayedExecute method is a method for delaying task execution in the ScheduledThreadPoolExecutor class. The source code is as follows.

private void delayedExecute(RunnableScheduledFuture<?> task) {  //If the current thread pool is closed / / execute the thread pool's reject policy if (isshutdown()) reject (task); / / the thread pool is not closed else {/ / add the task to the blocking queue super.getQueue().add(task) ; / / if the current thread pool is in SHUTDOWN state / / and the task cannot be executed in the current thread pool state / / and the task is successfully removed from the blocking queue if (isshutdown() & &! Canrunincurrentrunstate (task. Isperiodic()) & & remove (task)) / / the execution of the task is canceled, but the executing task task task.cancel (false) is not interrupted ; else / / call the ensueprestart() method in ThreadPoolExecutor class; ensueprestart();}}

You can see that the canRunInCurrentRunState method is called inside the delayedExecute method. The source code implementation of the canRunInCurrentRunState method is as follows.

boolean canRunInCurrentRunState(boolean periodic) {  return isRunningOrShutdown(periodic ? continueExistingPeriodicTasksAfterShutdown : executeExistingDelayedTasksAfterShutdown);}

You can see that the logic of the canRunInCurrentRunState method is relatively simple, which is to judge whether the thread pool can execute tasks in the current state.

In addition, the ensueprestart () method in the ThreadPoolExecutor class is called inside the delayedExecute method. Next, let's look at the implementation of the ensueprestart () method in the ThreadPoolExecutor class, as shown below.

void ensurePrestart() {  int wc = workerCountOf(ctl.get());  if (wc < corePoolSize)    addWorker(null, true);  else if (wc == 0)    addWorker(null, false);}

In the ensueprestart() method in the ThreadPoolExecutor class, first obtain the number of threads in the current thread pool. If the number of threads is less than corePoolSize, call the addWorker method to pass null and true. If the number of threads is 0, call the addWorker method to pass null and false.

For the source code analysis of the addWork() method, you can refer to the in the high concurrency topic< High concurrency -- through the source code of ThreadPoolExecutor class, we can deeply analyze the core process of thread pool executing tasks  > One article, here, will not be repeated.

reExecutePeriodic method

The source code of the reExecutePeriodic method is as follows.

void reExecutePeriodic(RunnableScheduledFuture<?> task) {  //If the thread pool is able to execute tasks in the current state (canrunincurrentrunstate (true)) {/ / put the tasks into the queue super.getQueue().add(task); / / tasks cannot be executed in the current state of the thread pool, and the tasks are successfully removed if (! Canrunincurrentrunstate (true) & & remove (task)) / / cancel the task. Cancel (false) ; else / / call the ensueprestart() method of ThreadPoolExecutor class; ensueprestart();}}

Generally speaking, the logic of the reExecutePeriodic method is relatively simple. However, we need to pay attention to the difference between the reExecutePeriodic method and the delayedExecute method: the task has been executed once when calling the reExecutePeriodic method, so the rejection policy of the thread pool will not be triggered; the task passed in the reExecutePeriodic method must be a periodic task.

onShutdown method

The onShutdown method is the hook function in the ThreadPoolExecutor class. It is invoked in the shutdown method in the ThreadPoolExecutor class, while the onShutdown method in the ThreadPoolExecutor class is an empty method, as shown below.

void onShutdown() {}

Copy code

The onShutdown method in the ThreadPoolExecutor class is implemented by subclasses, so the ScheduledThreadPoolExecutor class overrides the onShutdown method and implements the specific logic. The source code implementation of the onShutdown method in the ScheduledThreadPoolExecutor class is as follows.

@Overridevoid onShutdown() {  //Get queue BlockingQueue < runnable > q = super. Getqueue()// After the thread pool has called the shutdown method, whether to continue to execute the existing delayed task? Boolean keepdelayed = getexecuteexistingdelayedtasks aftershutdown policy()// After the thread pool has called the shutdown method, whether to continue to execute the existing scheduled task Boolean keepperiodic = getcontinueexistingperiodictasaftershutdown policy()// If (! Keepdelayed & &! Keepperiodic) {/ / traverse all the tasks in the queue for (object e: q.toarray()) / / cancel the task execution if (e instanceof RunnableScheduledFuture <? >) ((RunnableScheduledFuture <? >) e). Cancel (false) ; / / clear the queue q.clear();} / / after the thread pool has called the shutdown method, continue to execute the existing delayed tasks and scheduled tasks else {/ / traverse all tasks in the queue for (object e: q.toarray()) {/ / the current task is RunnableScheduledFuture type if (e instanceof runnableschedfuture) {/ / forcibly convert the task to RunnableScheduledFuture type RunnableScheduledFuture <? > t = (RunnableScheduledFuture <? >) e; / / delayed or periodic tasks that do not continue after the thread pool calls the shutdown method / / then delete and cancel the task from the queue if ((t.isperiodic()?! keepperiodic:! Keepdelayed) | t.iscancelled()) {if (q.remove (T)) t.cancel (false);}}}} / / finally call the tryTerminate() method tryTerminate();}

The main logic of the onShutdown method in the ScheduledThreadPoolExecutor class is to judge whether the thread pool will continue to execute the existing delayed tasks and scheduled tasks after calling the shutdown method. If it is no longer executed, the task will be cancelled and the queue will be emptied. If it continues to execute, the task in the queue will be forcibly converted to a RunnableScheduledFuture object, and then deleted from the queue and retrieved To eliminate tasks, we need to understand these two ways. Finally, we call the tryTerminate method of ThreadPoolExecutor class.

So far, we have finished analyzing the source code of the core method in the ScheduledThreadPoolExecutor class.

 

Tags: Concurrent Programming

Posted on Sun, 28 Nov 2021 06:00:44 -0500 by gc40