Original: https://www.cnblogs.com/dafanjoy/p/10904347.html
First, let's look at the core variables ThreadPoolExecutor uses to represent the state of the thread pool
//Used to mark thread pool state (3 bits high), number of threads (29 bits low) //The default is RUNNING state with 0 threads private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); //Number of threads mask bits private static final int COUNT_BITS = Integer.SIZE - 3; //Maximum number of threads (low 29 bits) 00011111111111111111111111 private static final int CAPACITY = (1 << COUNT_BITS) - 1; //(3 digits high): 11100000000000000000000000000 //Accept new tasks and handle tasks in the blocked queue private static final int RUNNING = -1 << COUNT_BITS; //(3 digits high): 00000000000000000000000000000 //Reject new tasks but handle tasks that are blocking the queue private static final int SHUTDOWN = 0 << COUNT_BITS; //(3 digits high): 00100000000000000000000000000 //Rejecting new tasks and discarding tasks blocking the queue interrupts the task being processed private static final int STOP = 1 << COUNT_BITS; //(3 digits high): 01000000000000000000000000000//All tasks completed (including those in the blocking queue). Current thread pool active thread is 0, terminated method will be called private static final int TIDYING = 2 << COUNT_BITS; //(3 digits high): 01100000000000000000000000000 //Termination state.State after terminated method call is completed private static final int TERMINATED = 3 << COUNT_BITS;// // Get High Three Running States private static int runStateOf(int c) { return c & ~CAPACITY; } //Get the number of low 29-bit threads private static int workerCountOf(int c) { return c & CAPACITY; } //Calculate new ctl values, thread status and number of threads private static int ctlOf(int rs, int wc) { return rs | wc; }
ThreadPoolExecutor Core Functions
//Submit Task Function void execute(Runnable command) //Functions to execute rejection policies void reject(Runnable command) //New WOKER Thread Function boolean addWorker(Runnable firstTask, boolean core) //WOKER Thread Execution Function void runWorker(Worker w) //Get Execute Task Function Runnable getTask() //Thread pool stop function void shutdown() //Thread pool stop function immediately void shutdownNow()
Next we share the execution flow and source code for the ThreadPoolExector thread pool class around these core functions
1. execute function
Excute is a function of ThreadPoolExector's submission task and the main three steps of its implementation are explained in the notes:
1. If the running thread is smaller than corePoolSize, try to create a new thread using a user-defined Runnalbe object. Calling the addWorker function checks runState and workCount atomically to prevent adding threads when they should not have been added by returning false.2.If a task can be successfully queued, a double check is required when adding a line city (because the thread died after the previous check), or when entering this method, the thread pool is shutdown, so the status needs to be checked again, rolled back when stopping, or created when the thread pool has no threadsA new thread.3. If it cannot be queued, a new thread needs to be added. If this operation fails, it means that the thread pool is shutdown or saturated, so the task is rejected
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task. The call to addWorker atomically checks runState and * workerCount, and so prevents false alarms that would add * threads when it shouldn't, by returning false. If the running thread is smaller than corePoolSize, try creating a new thread using a user-defined Runnalbe object. Calling the addWorker function checks runState and workCount for atomicity. Prevent adding threads when they should not be added by returning false. * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back the enqueuing if * stopped, or start a new thread if there are none. If a task is successfully queued, a double check is still required when adding a line city (because the thread died after the previous check). Or when you enter this method, the thread pool is shutdown, so you need to check the status again. If necessary, roll back into the queue when stopping. Or a new thread needs to be created when the thread pool has no threads. * 3. If we cannot queue task, then we try to add a new * thread. If it fails, we know we are shut down or saturated * and so reject the task. If you can't queue, you need to add a new thread. If this operation fails, it means that the thread pool is shutdown or saturated, so the task is rejected */ //Get the running state of the thread pool int c = ctl.get(); //Check Number of Core Threads if (workerCountOf(c) < corePoolSize) { //Execute the addWorker function if it is less than corePoolSize if (addWorker(command, true)) return; c = ctl.get(); } //Determines whether the current thread is in the Running state and whether its tasks can continue to join the workQueue queue (bounded or unbound task queue) if (isRunning(c) && workQueue.offer(command)) { //If the condition is met, state check again int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) //If the thread pool is no longer Running, remove the task from the queue and execute the closing policy reject(command); else if (workerCountOf(recheck) == 0) //If the number of workers is 0, add a worker addWorker(null, false); } else if (!addWorker(command, false))//Expand the thread pool based on maximumPoolSize if the queue is full //Add worker failed to execute rejection policy reject(command); }
From the code above, we can see that the execute function itself does not execute the submitted unnable task. The main purpose is to choose whether to execute the addWorker function or reject the reject rejection policy based on the state of the natural thread pool.
2. reject function
final void reject(Runnable command) { handler.rejectedExecution(command, this); } handler by RejectedExecutionHandler Specific implementation of the interface, the code below to execute the corresponding rejection policy is the specific implementation of each rejection policy
//If the maximum number of threads in the thread pool is reached, the policy places the tasks in the task queue in the caller thread to run. public static class CallerRunsPolicy implements RejectedExecutionHandler { /** * Creates a {@code CallerRunsPolicy}. */ public CallerRunsPolicy() { } /** * Executes task r in the caller's thread, unless the executor * has been shut down, in which case the task is discarded. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { //Execute Runnable's run method if the thread pool is still running r.run(); } } } //This policy throws exceptions directly, preventing the system from working properly; public static class AbortPolicy implements RejectedExecutionHandler { /** * Creates an {@code AbortPolicy}. */ public AbortPolicy() { } /** * Throw RejectedExecutionException directly */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); } } //Silence policy, no handling. public static class DiscardPolicy implements RejectedExecutionHandler { /** * Creates a {@code DiscardPolicy}. */ public DiscardPolicy() { } /** * Does nothing, which has the effect of discarding task r. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { } } //This policy discards the oldest task in the task queue, that is, the task that was first added to the current task queue and is about to be executed, and attempts to submit it it again. public static class DiscardOldestPolicy implements RejectedExecutionHandler { /** * Creates a {@code DiscardOldestPolicy} for the given executor. */ public DiscardOldestPolicy() { } /** * Obtains and ignores the next task that the executor * would otherwise execute, if one is immediately available, * and then retries execution of task r, unless the executor * is shut down, in which case task r is instead discarded. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { if (!e.isShutdown()) { //Remove the first task from the task queue directly e.getQueue().poll(); //Resubmit e.execute(r); } } }
3. addWorker function
The addworker function accomplishes two main functions:
1. Check the state of the thread pool and the current number of threads by cas, and increase the number of threads if it meets the criteria;
2. If the previous cas check succeeds and the number of threads has been added up, create a Worker object and bind to assign and start threads through the thread factory;
Let's look at the specific code
private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { //Get thread pool state int c = ctl.get(); int rs = runStateOf(c); if (rs >= SHUTDOWN && // Thread pool state is greater than or equal to SHUTDOWN, initial ctl is RUNNING, less than SHUTDOWN ! (rs == SHUTDOWN && //Thread pool state equal to SHUTDOWN firstTask == null && //The incoming task is empty ! workQueue.isEmpty())) //The workQueue queue is not empty return false; for (;;) { int wc = workerCountOf(c); //Gets the current number of worker threads if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) //Returns false if the current number of worker s is greater than the maximum capacity or the maximum number of threads set return false; if (compareAndIncrementWorkerCount(c))//cas to increase woker threads //cas successfully increased the number of threads out of order break retry; //cas fails to see if the thread pool state has changed c = ctl.get(); // Re-read ctl if (runStateOf(c) != rs) //Skip to the outer loop to recapture the thread pool state if changes occur, otherwise the inner loop re cas ts. continue retry; // else CAS failed due to workerCount change; retry inner loop } } //This represents CAS success, which means the number of threads has been added up, but the task has not yet started executing boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { //Create a worker object and create threads through a thread factory w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { final ReentrantLock mainLock = this.mainLock;//Lock to ensure synchronization of add worker actions as multiple threads may operate at the same time mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. //Re-check to ensure that the thread state is properly not closed int rs = runStateOf(ctl.get()); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable //Thread pool state exception throws throw new IllegalThreadStateException(); //Add worker workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { //Add worker succeeded, start thread t.start(); workerStarted = true; } } } finally { if (!workerStarted) //Failure handling if add fails addWorkerFailed(w); } return workerStarted; }
As you can see from the code above, new threads added to the thread pool will eventually be encapsulated as a Worker object, which will poll continuously to get tasks from the task queue and execute them through its bound threads. Let's look at the specific content of the Worker class
private final class Worker extends AbstractQueuedSynchronizer implements Runnable { /** * This class will never be serialized, but we provide a * serialVersionUID to suppress a javac warning. */ private static final long serialVersionUID = 6138294804551838833L; /** Thread this worker is running in. Null if factory fails. */ final Thread thread; /** Initial task to run. Possibly null. */ Runnable firstTask; /** Per-thread task counter */ volatile long completedTasks; /** * Creates with given first task and thread from ThreadFactory. * @param firstTask the first task (null if none) */ //Constructor that binds tasks and creates threads through a thread factory Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); } /** Delegates main run loop to outer runWorker */ //run method executes runWorker function public void run() { runWorker(this); } // Lock methods // // The value 0 represents the unlocked state. // The value 1 represents the locked state. protected boolean isHeldExclusively() { return getState() != 0; } // protected boolean tryAcquire(int unused) { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } protected boolean tryRelease(int unused) { setExclusiveOwnerThread(null); setState(0); return true; } public void lock() { acquire(1); } public boolean tryLock() { return tryAcquire(1); } public void unlock() { release(1); } public boolean isLocked() { return isHeldExclusively(); } void interruptIfStarted() { Thread t; if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { try { //Thread Interrupt t.interrupt(); } catch (SecurityException ignore) { } } } }
Threads are created through the thread factory in the Woker class constructor and the runWorker function is executed when the thread start s.
4. runWorker function
The runWork function mainly implements the while polling method to get the execution task through the getTask function. Let's take a look at the specific code analysis
final void runWorker(Worker w) { //Get Current Thread Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { //Polling to get the task executed through getTask() while (task != null || (task = getTask()) != null) { w.lock(); //(if the thread pool is at least stop state or (if the thread pool is at least stop state and the thread is interrupted)) and if the wt thread is interrupted if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); Throwable thrown = null; try { task.run();//Execute Tasks } 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 { processWorkerExit(w, completedAbruptly); } }
Execute the processWorkerExit function if you do not get the task
private void processWorkerExit(Worker w, boolean completedAbruptly) { // If the completedAbruptly value is true, an exception occurred during thread execution and the workerCount needs to be reduced by 1; // If no exceptions occur during thread execution, the workerCount has been reduced by 1 in the getTask() method, and there is no need to do so. if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted decrementWorkerCount(); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { //Count tasks completed completedTaskCount += w.completedTasks; // Removing from workers means removing a worker thread from the thread pool workers.remove(w); } finally { mainLock.unlock(); } // Determine whether to end a thread pool based on its state tryTerminate(); int c = ctl.get(); /* * When the thread pool is in RUNNING or SHUTDOWN state, if the worker ends abnormally, it addWorker directly; * If allowCoreThreadTimeOut=true and wait for the queue to have tasks, leave at least one worker; * If allowCoreThreadTimeOut=false, workerCount is no less than corePoolSize. */ 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); } }
After the processWorkerExit executes, the Worker thread is destroyed, which is the lifecycle of the entire Worker thread. Starting with the execute method, the Worker uses ThreadFactory to create a new Worker thread, runWorker gets the task through getTask, and then executes it. If getTask returns null, it enters the processWorkerExit methodThe main function of the tryTerminate() function is to determine if there are idle threads and set interrupts.
final void tryTerminate() { for (;;) { int c = ctl.get(); /* * Return directly when the current thread pool state is as follows: * 1. RUNNING,Because it's still running, it can't stop. * 2. TIDYING Or TERMINATED because there are no running threads in the thread pool; * 3. SHUTDOWN And wait for the queue to be not empty, then finish the task in the workQueue; */ if (isRunning(c) || runStateAtLeast(c, TIDYING) || (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty())) return; // If the number of threads is not zero, an idle worker thread is interrupted and returned if (workerCountOf(c) != 0) { // Eligible to terminate interruptIdleWorkers(ONLY_ONE); return; } final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // Here you try to set the state to TIDYING and call the terminated method if it is set successfully if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { try { // The terminated method does nothing by default, leaving it to the subclass implementation terminated(); } finally { // Set state to TERMINATED ctl.set(ctlOf(TERMINATED, 0)); termination.signalAll(); } return; } } finally { mainLock.unlock(); } // else retry on failed CAS } }
When the getTask function gets a task through workQueue.take(), it will always block if no interrupt is executed.In the shutdown method described below, all idle worker threads are interrupted, and if the worker thread is not idle when shutdown is executed, then the getTask method is called, then if there are no tasks in the workQueue, the call to workQueue.take() will always block.So each time the tryTerminate method is called at the end of a worker thread, it attempts to interrupt an idle worker thread, avoiding the situation where fetching tasks is always blocked when the queue is empty.
5. getTask functions
The getTask function accomplishes three main functions:
1. Check thread pool and Queue task status;
2. Control the number of threads based on maximumPoolSize, timeout, and queue tasks;
3. Get the task from the workQueue queue if the condition is met;
private Runnable getTask() { //Timeout flag boolean timedOut = false; // Did the last poll() time out? for (;;) { //Get thread pool state int c = ctl.get(); int rs = runStateOf(c); if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { //Returns NULL if thread pool status is abnormal or task queue is empty decrementWorkerCount(); return null; } //Get the current number of threads int wc = workerCountOf(c); // Are workers subject to culling? boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; /* * wc > maximumPoolSize The reason for this is that the setMaximumPoolSize method may have been executed at the same time as the method execution phase; * timed && timedOut If true, indicates that the current operation requires timeout control and that the last task retrieved from the blocked queue has timed out * Next, if the number of valid threads is greater than 1, or if the blocking queue is empty, try reducing the workerCount by 1; * If minus 1 fails, the retry is returned. * If wc == 1, then 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; } try { //Remove Task Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
6. shutdown function
The shutdown function is used to stop the thread pool. When shutdown is called, the thread pool will no longer accept new tasks, but the tasks in the work queue will still be executed, but the method returns immediately without waiting for the queue tasks to complete before returning.
public void shutdown() { final ReentrantLock mainLock = this.mainLock; mainLock.lock();//Locking try { //Check whether Shutdown is allowed checkShutdownAccess(); //Set thread pool state to SHUTDOWN advanceRunState(SHUTDOWN); //Interrupt the currently idle woker thread interruptIdleWorkers(); onShutdown(); // hook for ScheduledThreadPoolExecutor } finally { mainLock.unlock(); } tryTerminate(); }
7. shutdownNow function
When the shutdownNow function is called, the thread pool is immediately stopped, and tasks in the task queue are discarded and returned
public List<Runnable> shutdownNow() { List<Runnable> tasks; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { //Check whether Shutdown is allowed checkShutdownAccess(); //Set thread pool state to STOP advanceRunState(STOP); //Interrupt the currently idle woker thread interruptWorkers(); //Get Tasks in Current Queue tasks = drainQueue(); } finally { mainLock.unlock(); } tryTerminate(); return tasks; }
Here's a close look:
- In the getTask method, if the state of the thread pool is SHUTDOWN and the workQueue is empty, then null should be returned to end the worker thread, and the shutdown method needs to be called to bring the thread pool into the SHUTDOWN state;
- The shutdown method calls interruptIdleWorkers to interrupt idle threads, which hold mainLocks and iterate through the workers to determine whether they are idle one by one.But there is no mainLock in the getTask method;
- In getTask, if you determine that the current thread pool state is RUNNING and the blocking queue is empty, workQueue.take() is called to block;
- If, after determining that the current thread pool state is RUNNING, the shutdown method is called to change the state to SHUTDOWN, then if there is no interruption, the current worker thread will be blocked and not destroyed after calling workQueue.take(), because no new tasks are allowed to be added to the workQueue in the SHUTDOWN state, so the thread pool will always closeNo more;
- As you can see from the above, there are race conditions between shutdown method and getTask method when getting tasks from the queue.
- Thread interrupts are needed to solve this problem, which is why the interruptIdleWorkers method is used.When workQueue.take() is called, if the current thread is found to be in an interrupted state before or during execution, the InterruptedException is thrown to unblock the state.
- However, to interrupt a worker thread, it is also necessary to determine if the worker thread is idle and should not interrupt if the worker thread is working on a task.
- So Worker inherits from AQS and lock s when a worker thread processes a task. interruptIdleWorkers use tryLock when interrupting to determine if the worker thread is processing a task. If tryLock returns true, it indicates that the worker thread is not currently executing a task and can be interrupted.
Here's an analysis of the interruptIdleWorkers method.
private void interruptIdleWorkers() { interruptIdleWorkers(false); } private void interruptIdleWorkers(boolean onlyOne) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) { Thread t = w.thread; if (!t.isInterrupted() && w.tryLock()) { try { t.interrupt(); } catch (SecurityException ignore) { } finally { w.unlock(); } } if (onlyOne) break; } } finally { mainLock.unlock(); } }
interruptIdleWorkers iterates through all worker threads and interrupts them if tryLock is not interrupted successfully.
8. Summary
This paper makes a basic analysis and summary of the source code of the thread pool, which can be summarized as follows: 1. The thread pool records its running state and number of thread pool by CAS 2. Each thread is encapsulated as a WOKER thread object, and each worker thread can handle multiple tasks;3. Thread pool execution processes use their own state to determine whether a worker thread should be terminated or blocked waiting for new tasks. It also explains why worker threads are interrupted when the thread pool is closed and why each worker needs a lock.