Article catalog
summary
Java Review - Concurrent Programming_ Principle of ThreadPoolExecutor & source code analysis We reviewed the principle of Java thread pool ThreadPoolExecutor. ThreadPoolExecutor is only a part of the Executors tool class.
Next, let's introduce another part of the function, that is, the implementation of ScheduledThreadPoolExecutor, which is a thread pool that can schedule and execute tasks after a certain delay time is specified or scheduled.
Class structure

- Executors is actually a tool class. It provides many static methods that can return different thread pool instances according to the user's choice.
- ScheduledThreadPoolExecutor inherits ThreadPoolExecutor and implements ScheduledExecutorService interface.
- Thread pool queue is DelayedWorkQueue, which is similar to DelayedQueue and is a delay queue
- ScheduledFutureTask is a task with a return value, which is inherited from FutureTask. There is a variable state inside FutureTask to indicate the status of the task. The initial state is NEW and all States are NEW
private static final int NEW = 0; // Initial state private static final int COMPLETING = 1; // In execution private static final int NORMAL = 2; // Normal operation end status private static final int EXCEPTIONAL = 3; // Exception in operation private static final int CANCELLED = 4; // The task was cancelled private static final int INTERRUPTING = 5; // The task is being interrupted private static final int INTERRUPTED = 6; // The task has been interrupted
Possible task state transition paths are
NEN-> COMPLETING-> NORMAL//Initial status - > executing ー > normal settlement NEN-> COMPILETING-> EXCEPTIONAL//Initial status - > executing ー > execution exception NEN-> CANCELLED//Initial status I > task cancel NEN-> INTERRUPTING-> INTERRUPTED//Initial status - > interrupted - > interrupted
- There is also a variable period in ScheduledFutureTask to indicate the type of task. The task types are as follows period=0, indicating that the current task is one-time and exits after execution. period is a negative number, indicating that the current task is a fixed delay task, which is a fixed delay timed repeatable task. period is a positive number, indicating that the current task is a fixed rate task, which is a regular repeatable task with a fixed frequency

- A constructor of ScheduledThreadPoolExecutor is as follows. It can be known from this constructor that the thread pool queue is DelayedWorkQueue.
/** * Creates a new {@code ScheduledThreadPoolExecutor} with the * given core pool size. * * @param corePoolSize the number of threads to keep in the pool, even * if they are idle, unless {@code allowCoreThreadTimeOut} is set * @throws IllegalArgumentException if {@code corePoolSize < 0} */ // Use the modified DelayQueue public ScheduledThreadPoolExecutor(int corePoolSize) { // Call the constructor of the parent class ThreadPoolExecutor super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue()); }
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); }
Core method & source code analysis
schedule(Runnable command, long delay,TimeUnit unit)
The function of this method is to submit a delayed task. The task starts to be executed after the delay time in unit is calculated from the submission time. The submitted task is not a periodic task. The task will be executed only once
/** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} */ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) { // 1 parameter verification if (command == null || unit == null) throw new NullPointerException(); // 2 task conversion RunnableScheduledFuture<?> t = decorateTask(command, new ScheduledFutureTask<Void>(command, null, triggerTime(delay, unit))); // 3 add task to delay queue delayedExecute(t); return t; }
Above, we analyzed how to add tasks to the delay queue. Next, let's look at how the threads in the thread pool get and execute tasks.
When we talked about ThreadPoolExecutor earlier, we said that the specific thread executing the task is the Worker thread, which calls the run method of the specific task to execute. Since the task here is ScheduledFutureTask, let's take a look at the run method of ScheduledFutureTask
/** * Overrides FutureTask version so as to reset/requeue if periodic. */ public void run() { // 8 is it executed only once boolean periodic = isPeriodic(); // 9 cancel task if (!canRunInCurrentRunState(periodic)) cancel(false); // 10 execute only once and call the schedule method else if (!periodic) ScheduledFutureTask.super.run(); // 11 regular execution else if (ScheduledFutureTask.super.runAndReset()) { // 11.1 set time=time+period setNextRunTime(); // 11.2 rejoin the task to the delay queue reExecutePeriodic(outerTask); } }
When will multiple threads execute CAS at the same time to transition the status of the current task from NEW to COMPLETING? In fact, this happens when the same command is submitted to the thread pool multiple times, because the same task shares a state value state.
If the task fails, execute the code (13.1). The code of setException is as follows, which is similar to the set function.
protected void setException(Throwable t) { // If the status of the current task is NEW, it is set to COMPLETING if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { outcome = t; // Set the status of the current task to EXCEPTIONAL, that is, the task ends abnormally UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state finishCompletion(); } }
Here, the logic of code (10) is executed, and the one-time task is executed
scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit)
The function of this method is to delay the execution of the task for a fixed time and run it again (fixed delay task)
- initialDelay indicates how long it is delayed to start executing the task command after submitting the task
- delay indicates how much time is extended after the task is executed, and then run the command task again
- Unit is the time unit of initialDelay and delay
The task will run repeatedly until an exception is thrown, cancelled, or the thread pool is closed.
/** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} */ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { // 14 parameter verification if (command == null || unit == null) throw new NullPointerException(); if (delay <= 0) throw new IllegalArgumentException(); // 15. For task conversion, note that poeriod = - deal < 0 [unit.toNanos(-delay)] ScheduledFutureTask<Void> sft = new ScheduledFutureTask<Void>(command, null, triggerTime(initialDelay, unit), unit.toNanos(-delay)); RunnableScheduledFuture<Void> t = decorateTask(command, sft); sft.outerTask = t; // 16 add task to queue delayedExecute(t); return t; }
- Code (14) performs parameter verification. If the verification fails, an exception is thrown
- Code (15) converts the command task to ScheduledFutureTask. Note here that the value of the period variable passed to ScheduledFutureTask is - delay, and period < 0 indicates that the task is a repeatable task.
- Then code (16) adds the task to the delay queue and returns.
When the task is added to the delay queue, the thread pool thread gets the task from the queue and then calls the run method of ScheduledFutureTask to execute it. Since period < 0 here, isPeriodic returns true, so execute the code (11). The code of runAndReset is as follows.

/** * Executes the computation without setting its result, and then * resets this future to initial state, failing to do so if the * computation encounters an exception or is cancelled. This is * designed for use with tasks that intrinsically execute more * than once. * * @return {@code true} if successfully run and reset */ protected boolean runAndReset() { // 17 if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return false; // 18 boolean ran = false; int s = state; try { Callable<V> c = callable; if (c != null && s == NEW) { try { c.call(); // don't set result ran = true; } catch (Throwable ex) { setException(ex); } } } finally { // runner must be non-null until state is settled to // prevent concurrent calls to run() runner = null; // state must be re-read after nulling runner to prevent // leaked interrupts s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } return ran && s == NEW; }
This code is similar to the run method of FutureTask, except that the status of the task will not be set after the normal execution of the task. This is to make the task repeatable.
Code (19) is added here. This code judges that if the current task is completed normally and the task status is NEW, it returns true, otherwise it returns false. If true is returned, the setNextRunTime method of the execution code (11.1) sets the next execution time of the task.
/** * Sets the next time to run for a periodic task. */ private void setNextRunTime() { long p = period; if (p > 0) // Ffixed rate type task time += p; else // Fixed delay type task time = triggerTime(-p); }
Here, p < 0 indicates that the current task is a fixed delay task. Then set time to the current time plus - p time, that is, execute again after delaying - p time.
The execution principle of fixed delay type tasks is: after adding a task to the delay queue and waiting for the initialDelay time, the task will expire, and the expired task will be removed from the queue and executed. After execution, the delay time of the task will be reset, and then the task will be placed in the delay queue and cycle. It should be noted that if a task throws an exception during execution, the task will end, but it will not affect the execution of other tasks.
scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)
This method calls the specified task (fixed rate task) at a fixed frequency relative to the starting time point. When the task is submitted to the thread pool and the initialDelay time is delayed (time unit: Unit) and execute the task command. Then execute again from the initialDelay+period time point, and then execute again at the initialDelay + 2 * period time point. Cycle until an exception is thrown, the cancel method of the task is called to cancel the task, or the thread pool is closed.
The principle of scheduleAtFixedRate is similar to that of scheduleWithFixedDelay. Let's take a look at the differences between them.
The code for calling scheduleAtFixedRate first is as follows
/** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} */ public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { if (command == null || unit == null) throw new NullPointerException(); if (period <= 0) throw new IllegalArgumentException(); // For decoration tasks, note that period = period > 0 here is not negative ScheduledFutureTask<Void> sft = new ScheduledFutureTask<Void>(command, null, triggerTime(initialDelay, unit), unit.toNanos(period)); RunnableScheduledFuture<Void> t = decorateTask(command, sft); sft.outerTask = t; delayedExecute(t); return t; }
In the above code, when converting a fixed rate task command to ScheduledFutureTask, set period=period instead of - period.
So when the current task is executed, the time when the setNextRunTime is called to set the next execution time is time += p instead of time = triggerTime(-p).
Summary: compared with the fixed delay task, the fixed rate execution rule is that the task is started when the time is initdelay + n * period. However, if the current task has not been executed and the next task execution time is up, it will not be executed concurrently. The next task to be executed will be delayed and will not be executed until the current task is executed.
Summary
The implementation principle of ScheduledThreadPoolExecutor, which internally uses DelayQueue to store specific tasks. There are three types of tasks, in which one-time tasks are completed after execution. Fixed delay tasks ensure a fixed time interval between multiple executions of the same task, and fixed rate tasks ensure that they are executed at a fixed frequency. Task types are distinguished by the value of period.
