This article gives a detailed analysis of how ThreadPool Executor creates thread pools to operate thread lifecycles through source code. By explaining execute method, addWorker method, Worker class, runWorker method, getTask method and processWorkerExit from the source code point of view, there are colored eggs at the end of the article.
Exexct method
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); /** * workerCountOf Method The value of 29 bits is taken out to indicate the number of threads currently active. * If the number of currently active threads is less than corePoolSize, a new thread is created and placed in the thread pool, and the task is placed in the thread. */ if (workerCountOf(c) < corePoolSize) { /** * addWorker The second parameter indicates whether to limit the number of threads added based on corePoolSize or maximumPoolSize. * If true, judge by corePoolSize * If it's false, judge by maximum PoolSize */ if (addWorker(command, true)) return; /** * If the addition fails, retrieve the ctl value */ c = ctl.get(); } /** * If the thread pool is in Running state and the task is added to the queue */ if (isRunning(c) && workQueue.offer(command)) { //double-check, retrieve the value of ctl int recheck = ctl.get(); /** * Once again determine the state of the thread pool, if not the running state, because the command has been added to the blocking queue before, it is necessary to remove the command from the queue at this time; * The task is handled by handler using the rejection strategy, and the whole method returns */ if (!isRunning(recheck) && remove(command)) reject(command); /** * Gets the number of valid threads in the thread pool and executes the addWorker method if the number is 0. * The first parameter is null, which means creating a thread in the thread pool but not starting it. * The second parameter, false, sets the maximum number of threads in the thread pool to maximumPoolSize, which is judged by maximumPoolSize when adding threads. */ else if (workerCountOf(recheck) == 0) addWorker(null, false); /** * There are two ways to implement this: * 1,The state of the thread pool is not RUNNING; * 2,Thread pool status is RUNNING, but workerCount >= corePoolSize, workerQueue is full * At this point, the addWorker method is called again, and the second parameter passes false, setting the upper limit of the limited number of threads in the thread pool to maximumPoolSize. * If it fails, the rejection strategy is implemented. */ } else if (!addWorker(command, false)) reject(command); }
Simply put, when execute() is executed, if the state is always RUNNING, the execution process is as follows:
- If workerCount < corePoolSize, a thread is created and started to perform the newly committed task
Affairs;
- If workerCount >= corePoolSize and the blocking queue in the thread pool is not full, the task is added
Add to the blocking queue;
- If workerCount >= corePoolSize & workerCount<
Maximum PoolSize, and the blocking queue in the thread pool is full, creates and starts a thread to execute the new
Tasks submitted;
- If workerCount >= maximumPoolSize and the blocking queue in the thread pool is full, then the root
The task is handled according to the rejection policy, and the default way is to throw an exception directly.
Note here addWorker(null, false); that is, to create a thread, but there is no incoming task because
Tasks have been added to the workQueue, so when the worker executes, it will be directly from the workQueue
Get the task. So, add Worker (null, false) is executed when workerCountOf(recheck) == 0; and so is it.
In order to ensure that the thread pool is in RUNNING state, there must be a thread to perform tasks.
addWorker method
The main function of the addWorker method is to create a new thread in the thread pool and execute it. The first Task parameter is used to specify the first task to be performed by the new thread. The core parameter is true, which indicates whether the current number of active threads is less than the corePoolSize when the new thread is added. The false indicates that the current activity needs to be judged before the new thread is added. Is the number of active threads less than maximumPoolSize
private boolean addWorker(Runnable firstTask, boolean core) { retry: /** * Because in the process of thread execution, all kinds of situations are likely to be in, the increase of worker is guaranteed by spinning. */ for (; ; ) { int c = ctl.get(); //Get the thread pool running status int rs = runStateOf(c); /** * * If RS >= SHUTDOWN, it means that no new tasks are received at this time. * Next, three conditions pass through & connect and return false as long as one task is not satisfied. * 1.rs == SHUTDOWN,Represents a closed state that no longer receives submitted tasks, but can continue to process tasks that have been saved in the blocking queue; * 2.fisrtTask Empty * 3.Check if queue empty only if necessary. */ if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty())) return false; for (; ; ) { //Get the number of threads in the thread pool int wc = workerCountOf(c); /** * If the number of threads >= CAPACITY, that is, the maximum value of ctl's low 29 bits, then false is returned. * Here core is used to determine whether the maximum limit for the number of threads is corePoolSize or maximumPoolSize. * If core is ture, it is compared according to corePoolSize. * If core is false, it is compared according to maximum PoolSize. */ if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; /** * Increase the number of threads by CAS atoms. * If successful, jump out of the first for loop. */ if (compareAndIncrementWorkerCount(c)) break retry; c = ctl.get(); // Re-read ctl //If the current running state is not equal to rs, indicating that the state of the thread pool has changed, the first for loop is returned to continue execution. if (runStateOf(c) != rs) continue retry; // else CAS failed due to workerCount change; retry inner loop } } boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { //Create Worker objects based on firstTask w = new Worker(firstTask); //Each Worker object creates a thread final Thread t = w.thread; if (t != null) { //Create reentrant locks final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // Get the state of the thread pool int rs = runStateOf(ctl.get()); /** * The state of thread pool is less than that of SHUTDOWN, indicating that the thread pool is in RUNNING state. * If rs is RUNNING state or rs is SHUTDOWN state and first Task is null, add threads to the thread pool; * Because no new tasks will be added when SHUTDOWN is in state, but tasks in workQueue will still be processed. */ if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); //workers is a hashSet workers.add(w); int s = workers.size(); //largestPoolSize records the maximum number of threads that appear in the thread pool if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { //Start the thread, and Worker implements the Running method, which calls Worker's run method t.start(); workerStarted = true; } } } finally { if (!workerStarted) addWorkerFailed(w); } return workerStarted; }
Worker class
Each object in the thread pool is encapsulated as a Worker object, and ThreadPool maintains a set of Worker objects.
The Worker class inherits AQS and implements the Runnable interface, which contains two important attributes: firstTask is used to save incoming tasks, threads are threads created by calling constructors through ThreadFactory and threads used to process tasks.
private final class Worker extends AbstractQueuedSynchronizer implements Runnable { final Thread thread; Runnable firstTask; volatile long completedTasks; Worker(Runnable firstTask) { /** * Set the state to - 1 to prevent interruption until the runWorker method is called. * Because the default state of AQS is 0, it should not be interrupted if a Worker object has just been created and the task has not yet been executed. */ setState(-1); this.firstTask = firstTask; /** * Create a thread, and the parameter passed in by the newThread method is this, because Worker itself inherits the Runnable interface, which is a thread. * So a Worker object calls the run method in the Worker class at startup */ this.thread = getThreadFactory().newThread(this); } }
The Worker class inherits AQS and uses AQS to implement exclusive locks. Why not use ReentrantLock?
You can see the tryAcquire method, which does not allow reentrant, while ReentrantLock allows reentrant:
- Once the lock method acquires the exclusive lock, it indicates that the current thread is executing the task.
- If a task is being performed, threads should not be interrupted.
- If the thread is not in the state of exclusive lock, that is, idle state, it does not handle the task, then the thread can be interrupted.
- When the shutdown method or tryTerminate method is executed in the thread pool, the interruptIdleWorkers method is called to interrupt the idle thread. The interruptIdleWorkers method uses the tryLock method to determine whether the thread in the thread pool is idle or not.
- It is set to non-reentrant because the running threads are not interrupted when a task calls a thread pool control method such as setCorePoolSize
Therefore, Worker inherits from AQS to determine whether the thread is idle and whether it is interrupted.
protected boolean tryAcquire(int unused) { /** * cas Modify state, not reentrant; * state Judging by 0, the Worker constructor says that state is set to - 1 to prohibit interruption of threads before executing tasks. * Therefore, in the runWorker method, the unlock method of the Worker object is called first, and the state is set to 0. */ if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; }
runWorker Method
The run method in the Worker class calls the runWorker method to perform tasks
final void runWorker(Worker w) { Thread wt = Thread.currentThread(); //Get the first task Runnable task = w.firstTask; w.firstTask = null; //Allow interruption w.unlock(); //Whether to exit the loop due to an exception boolean completedAbruptly = true; try { //If task is empty, the task is retrieved through getTask while (task != null || (task = getTask()) != null) { w.lock(); /** * If the thread pool is stopping, the interrupt state of the current thread should be ensured. * If not, make sure that the current thread is not interrupted */ if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { //beforeExecute and afterExecute are left to subclasses for implementation beforeExecute(wt, task); Throwable thrown = null; try { //Execution through tasks, not threads task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { //ProceWorkerExit judges completedAbruptly to indicate whether there is an exception during execution processWorkerExit(w, completedAbruptly); } }
Summarize the execution process of the runWorker method:
- The while loop continuously obtains tasks through the getTask method.
- The getTask method retrieves tasks from blocking queues.
- If the thread pool is stopping, make sure that the current thread is interrupted, otherwise make sure that the current thread is not interrupted.
- Call task.run() to execute the task;
- If task is null, it will jump out of the loop and execute the processWorkerExit method.
- When the runWorker method is executed, it also means that the runWorker method is executed and the thread is destroyed.
getTask method
The getTask method is used to retrieve tasks from blocked queues
private Runnable getTask() { //The value of the timeout variable indicates whether the task last retrieved from the blocking queue timed out boolean timedOut = false; for (; ; ) { int c = ctl.get(); int rs = runStateOf(c); /** * If RS >= SHUTDOWN indicates that the thread pool is not RUNNING, it needs to be judged again: * 1,rs >= STOP ,Is the thread pool STOP? * 2,Is the blocking queue empty * If one of the above conditions is satisfied, the workCount is subtracted by one and null is returned. * Because if the current thread pool status is STOP or above or the queue is empty, the task cannot be retrieved from the blocking queue. */ if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } int wc = workerCountOf(c); /** * timed Variables are used to determine whether timeout control is needed. * allowCoreThreadTimeOut The default is false, which means that core threads are not allowed to timeout. * wc > corePoolSize,Represents that the current number of threads is greater than the number of core threads; * For those threads that exceed the number of core threads, timeout control is required. */ boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; /** * wc > maximumPoolSize In this case, the setMaximum PoolSize method may be executed at the same time during the execution phase of the method. * timed && timedOut If true, it means that the current operation needs timeout control, and the last time the task was retrieved from the blocking queue, the timeout occurred. * Next, if the number of effective Xianheng is more than 1, or the workQueue is empty, then we will try to reduce the workCount by 1. * If subtraction 1 fails, retry is returned. * If wc==1, it means that the current thread is the only thread in the thread pool. */ if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } /** * timed For trure, the poll method of workQueue is used to control the timeout. If the task is not acquired in the keep AliveTime time, null is returned. * Otherwise, through the take method, if the queue is empty, the take method will block until the queue is not empty. */ try { Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; //If r==null, it indicates that the time has expired, timedOut = true; timedOut = true; } catch (InterruptedException retry) { //If the current thread interrupts when the task is fetched, timedOut = false is used. timedOut = false; } } }
Note: The second if judgement is designed to control the number of valid threads in the thread pool.
In the execute method, if the number of threads in the current thread pool exceeds coolPoolSize and is less than maxmumPoolSize, and when the blocking queue is full, worker threads can be added. But if the worker thread does not get the task in the timeout time, timeOut=true indicates that the workQueue is empty, which means that the current thread pool does not need so many threads to execute the task. It can destroy more threads than the number of corePoolSize and ensure that the number of threads is in corePoolSize.
When will the thread be destroyed?
Of course, after the runWorker method is executed, that is, after the run method in the Worker is executed, it is automatically reclaimed by the JVM.
ProceWorkerExit method
private void processWorkerExit(Worker w, boolean completedAbruptly) { /** * If completedAbruptly is true, it means that an exception occurs during thread execution and the number of workerCount s needs to be reduced by one. * If completedAbruptly is false, the workerCount has been subtracted by one in the getTask method. There is no need to subtract here. */ if (completedAbruptly) decrementWorkerCount(); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { //Statistics of completed tasks completedTaskCount += w.completedTasks; //Removing a worker means removing a worker thread from the thread pool workers.remove(w); } finally { mainLock.unlock(); } //Hook function to determine whether the thread pool is terminated or not based on the state of the thread pool tryTerminate(); int c = ctl.get(); /** * When the current thread is RUNNING or SHUTDOWN, if the worker ends abnormally, it addWorker directly. * If allowCoreThreadTimeOut=true, the waiting queue has tasks, leaving at least one worker; * If allowCoreThreadTimeOut=false, workerCount is less than coolPoolSize */ if (runStateLessThan(c, STOP)) { if (!completedAbruptly) { int min = allowCoreThreadTimeOut ? 0 : corePoolSize; if (min == 0 && !workQueue.isEmpty()) min = 1; if (workerCountOf(c) >= min) return; // replacement not needed } addWorker(null, false); } }
At this point, after the processWorkerExit is executed, the worker thread is destroyed.
Work execution process
The lifecycle of a worker thread begins with the execute method. Worker uses ThreadFactory to create a new worker thread. runWorker gets the task through getTask, and then executes the task. If getTask returns null and enters processWorkerExit, the whole thread ends.
Not paying attention to my public number yet?
- At the end of the scan, the two-dimensional code pays attention to the public number [Xiaoqiang's way to advance], which can be obtained as follows:
- Learning materials: 1T video tutorial: covering Java web front-end and back-end teaching videos, machine learning/artificial intelligence teaching videos, Linux system tutorial videos, IELTS video tutorials;
- More than 100 books: including C/C++, Java, Python three programming languages must see the classic books, LeetCode problem-solving complete;
- Software tools: Almost all of the software you might use on your programming path;
- Project source code: 20 Java Web project source code.