Thread pooling technology in Java typically uses Executors, a factory class that provides a very simple way to create various types of thread pools:
public static ExecutorService newFixedThreadPool(int nThreads) public static ExecutorService newSingleThreadExecutor() public static ExecutorService newCachedThreadPool() public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
The core interface is Executor, which has only one execute method abstracted as execution of tasks (Runnable interface). ExecutorService interface provides life cycle management of task execution based on Executor, mainly submit and shutdown methods. AbstractExecutorService has some parties to ExecutorServiceMethod is implemented by default, mainly submit and invoke methods. Executor interface execute method for true task execution is implemented by subclass, ThreadPoolExecutor, which implements the task execution framework based on thread pool, so to understand the thread pool of JDK, you must first look at this class.
Before looking at the execute method, you need to introduce several variables or classes.
ctl
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
This variable is the core of the whole class. AtomicInteger guarantees that manipulation of this variable is atomic. ThreadPoolExecutor uses this variable to save two things with clever manipulation:
- Number of all valid threads
- Status of individual threads (runState)
Low 29-bit memory threads, high 3-bit memory runState, so runState has five values:
- RUNNING:-536870912
- SHUTDOWN:0
- STOP:536870912
- TIDYING:1073741824
- TERMINATED:1610612736
The transition between states in a thread pool is complex. Just remember the following:
- RUNNING status: The thread pool is functioning properly and can accept new tasks and process tasks in the queue;
- SHUTDOWN status: No longer accepts new tasks, but executes tasks in the queue;
- STOP Status: No more new tasks are accepted, no tasks in the queue are processed
There are operations around the ctl variable, and understanding these methods is the basis for understanding some of the obscure code that follows:
/** * This method is used to take out the runState value because the CAPACITY value is: 00011111111111111111111 * ~For bitwise inversion, ~CAPACITY is 11100000000000000000000000000000 * If you do this with the same parameter, the position will be 0 lower than 29, and the value of runState will remain the same 3 digits higher. * * @param c * This parameter is the int value that stores runState and workerCount * @return runState Value of */ private static int runStateOf(int c) { return c & ~CAPACITY; } /** * This method is used to take out the value of workerCount * Since the CAPACITY value is: 00011111111111111111111111 11, &the operation will set the parameter at the top 3 position of 0 * Keep the low 29 bits of the parameter, which is the value of the workerCount * * @param c * ctl, Store run state and workerCount int values * @return workerCount Value of */ private static int workerCountOf(int c) { return c & CAPACITY; } /** * Save runState and workerCount in the same int * "|"The meaning of the operation is that if the value of rs is 101000 and the value of wc is 000111, their bit or operation is 101111 * * @param rs * runState Shifted values, responsible for filling the high 3 bits of the returned value * @param wc * workerCount Shifted value, responsible for filling the lower 29 bits of the returned value * @return Both or the calculated value */ private static int ctlOf(int rs, int wc) { return rs | wc; } // only RUNNING The state will be less than 0 private static boolean isRunning(int c) { return c < SHUTDOWN; }
corePoolSize
Core thread pool size, active threads less than corePoolSize are created directly, greater than or equal to are added to workQueue first, and queues are full before new threads are created.
keepAliveTime
Threads get the task's timeout from the queue, which means they terminate if the thread is idle for more than that time.
Worker
private final class Worker extends AbstractQueuedSynchronizer implements Runnable ...
The internal class Worker is an encapsulation of tasks, all submit's Runables are encapsulated as Workers, it's also a Runnable, and then uses the AQS framework (see me about AQS) This article ) A simple, non-reentrant mutex is implemented. The main purpose of mutex is to determine whether a thread is idle or running when it is interrupted. You can see the analysis of shutdown and shutdownNow methods later.
// state Only 0 and 1, mutually exclusive protected boolean tryAcquire(int unused) { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true;// Lock acquired successfully } // Threads enter the waiting queue return false; } protected boolean tryRelease(int unused) { setExclusiveOwnerThread(null); setState(0); return true; }
ReentrantLock is not used to avoid modifying thread pool variables, such as setCorePoolSize, in the code for task execution because ReentrantLock is reentrant.
execute
The execute method consists of three main steps:
- Create a new thread when the active thread is less than corePoolSize;
- When the active thread is larger than corePoolSize, it joins the task queue first.
- The task queue is full before starting a new thread and rejects the task if the maximum number of threads is reached.
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); int c = ctl.get(); // Number of active threads < corePoolSize if (workerCountOf(c) < corePoolSize) { // Start a new thread directly.Second parameter true:addWorker Will be re-checked workerCount Is it less than corePoolSize if (addWorker(command, true)) // Add Successful Return return; c = ctl.get(); } // Number of active threads >= corePoolSize // runState by RUNNING && Queue not full if (isRunning(c) && workQueue.offer(command)) { int recheck = ctl.get(); // double check // wrong RUNNING The state is from workQueue Remove task from and reject if (!isRunning(recheck) && remove(command)) reject(command);// Task rejection using thread pool specified policy // Thread pool is in RUNNING state || Thread pool is not RUNNING Status but task removal failed else if (workerCountOf(recheck) == 0) // This line of code is for SHUTDOWN There are no active threads in the state, but there are tasks in the queue that do not perform this special case. // Add one null Tasks are due to SHUTDOWN In state, the thread pool no longer accepts new tasks addWorker(null, false); // Two cases: // 1.wrong RUNNING Status rejects new tasks // 2.Queue full failed to start new thread( workCount > maximumPoolSize) } else if (!addWorker(command, false)) reject(command); }
When the comment is clear, it is no longer explained, and the harder part to understand should be addWorker(null, false); this line, combined with addWorker.The primary purpose is to prevent the HUTDOWN state from losing active threads, but there are tasks in the queue that do not perform this special case.
addWorker
This method is hard to understand.
private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c);// Current Thread Pool Status // Check if queue empty only if necessary. // This statement is equivalent: rs >= SHUTDOWN && (rs != SHUTDOWN || firstTask != null || // workQueue.isEmpty()) // Return directly if the following price adjustments are met false,Thread creation failed: // rs > SHUTDOWN:STOP || TIDYING || TERMINATED New tasks are no longer accepted and all tasks are completed // rs = SHUTDOWN:firtTask != null Tasks are no longer accepted at this time, but queued tasks are still executed // rs = SHUTDOWN:firtTask == null see execute Methodological addWorker(null, // false),Task is null && Queue empty // The last case is that SHUTDONW In the state, if the queue is not empty, it has to be executed further down. Why? add One null What is the purpose of the task? // see execute Method only workCount==0 When firstTask Will be null The condition here is the thread pool SHUTDOWN No more new assignments // But at this point the queue is not empty, and you have to create a thread to finish executing the task. if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty())) return false; // When you get here: // 1.Thread pool status is RUNNING // 2.SHUTDOWN Status, but there are tasks in the queue that need to be executed for (;;) { int wc = workerCountOf(c); if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; if (compareAndIncrementWorkerCount(c))// Incremental Atomic Operations workCount break retry;// A retry loop that the operation successfully jumped out of c = ctl.get(); // Re-read ctl if (runStateOf(c) != rs)// Retry if the state of the thread pool changes continue retry; // else CAS failed due to workerCount change; retry inner loop } } // wokerCount Incremental Success boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { final ReentrantLock mainLock = this.mainLock; w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { // Concurrent access thread pool workers Object must be locked mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. int c = ctl.get(); int rs = runStateOf(c); // RUNNING state || SHUTDONW Clean up remaining tasks in queue in state if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); // Add a newly started thread to the thread pool workers.add(w); // To update largestPoolSize int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } // Start a newly added thread that executes first firstTask,Then keep taking tasks from the queue to execute // When waiting keepAlieTime The thread ends without task execution.see runWoker and getTask Code for the method. if (workerAdded) { t.start();// The final execution is ThreadPoolExecutor Of runWoker Method workerStarted = true; } } } finally { // If the thread fails to start, it starts from wokers Remove in w And decreasing wokerCount if (!workerStarted) // Decreasing wokerCount Will trigger tryTerminate Method addWorkerFailed(w); } return workerStarted; }
runWorker
When a task is added successfully, it actually executes the runWorker method, which is very important. In short, what it does is:
- The first startup performs the initialization of the incoming task firstTask;
- Tasks are then taken from the workQueue to execute, and if the queue is empty, wait so long for keepAliveTime.
final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; // Worker Thread interrupts are suppressed in the constructor setState(-1),So here's what you need unlock This allows interruptions w.unlock(); // Used to identify abnormal termination, finally in processWorkerExit Methods have different logic // by true Situation: 1.Executing the task threw an exception;2.Interrupted. boolean completedAbruptly = true; try { // If getTask Return null that getTask The Central Committee will workerCount Decrease, if this decrement is exceptional it will processWorkerExit Medium processing while (task != null || (task = getTask()) != null) { w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { // Some processing can be inserted before the task executes, and subclasses overload the method beforeExecute(wt, task); Throwable thrown = null; try { task.run();// Perform user 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 { // and beforeExecute Same, leave subclasses to overload afterExecute(task, thrown); } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { // End some thread cleanup processWorkerExit(w, completedAbruptly); } }
getTask
private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. // 1.rs > SHUTDOWN therefore rs At least equal to STOP,The queued tasks are no longer processed at this time // 2.rs = SHUTDOWN therefore rs>=STOP Definitely not, you will also need to process the tasks in the queue unless the queue is empty // Both cases return null Give Way runWoker Sign out while Loop is the end of the current thread, so you must decrement // wokerCount if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { // Decreasing workerCount value decrementWorkerCount(); return null; } // Mark if timeout is set when taking tasks from the queue boolean timed; // Are workers subject to culling? // 1.RUNING state // 2.SHUTDOWN Status, but there are tasks in the queue that need to be executed for (;;) { int wc = workerCountOf(c); // 1.core thread Allow timeout, then exceed corePoolSize Threads of must have timed out // 2.allowCoreThreadTimeOut == false && wc > // corePoolSize This is usually the case. core thread Even if idle, it will not be recycled, as long as it exceeds the thread timed = allowCoreThreadTimeOut || wc > corePoolSize; // from addWorker You can see the general wc Will not be greater than maximumPoolSize,So more concerned about the second half of the sentence: // 1. timedOut == false The first execution cycle, removing tasks from the queue is not null Method returns or // poll An exception occurred and the retry was attempted // 2.timeOut == true && timed == // false:Look at the code behind workerQueue.poll Timeout timeOut Only then true, // also timed To be false,These two conditions are not valid at the same time (since there is a timeout then) timed Definitely true) // So the timeout will not continue executing but return null End thread.(Important: How do threads time out??) if (wc <= maximumPoolSize && !(timedOut && timed)) break; // workerCount Decrease, end current thread if (compareAndDecrementWorkerCount(c)) return null; c = ctl.get(); // Re-read ctl // The state of the thread pool needs to be rechecked because the thread pool may be SHUTDOWN if (runStateOf(c) != rs) continue retry; // else CAS failed due to workerCount change; retry inner loop } try { // 1.Tasks taken from the queue with a specified timeout // 2.core thread No timeout Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true;// overtime } catch (InterruptedException retry) { timedOut = false;// Thread interrupted retry } } }
processWorkerExit
Thread exit does some cleanup in this way.
private void processWorkerExit(Worker w, boolean completedAbruptly) { // If normal then runWorker Of getTask Method workerCount Has been subtracted if (completedAbruptly) decrementWorkerCount(); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // Cumulative threaded completedTasks completedTaskCount += w.completedTasks; // Remove threads with timeouts or exceptions from the thread pool workers.remove(w); } finally { mainLock.unlock(); } // Attempt to stop thread pool tryTerminate(); int c = ctl.get(); // runState by RUNNING or SHUTDOWN if (runStateLessThan(c, STOP)) { // Thread does not end abnormally if (!completedAbruptly) { // Thread pool minimum idle count, allowed core thread Timeout is 0, otherwise it is corePoolSize int min = allowCoreThreadTimeOut ? 0 : corePoolSize; // If min == 0 However, the queue is not empty to ensure that there is a thread to perform the tasks in the queue if (min == 0 && !workQueue.isEmpty()) min = 1; // Thread pool is not empty, so don't worry if (workerCountOf(c) >= min) return; // replacement not needed } // 1.Thread exited abnormally // 2.The thread pool is empty, but there are still tasks in the queue not executed. Look addWoker Methods for handling this situation addWorker(null, false); } }
tryTerminate
The processWorkerExit method attempts to call tryTerminate to terminate the thread pool.This method is executed after any action that may cause the thread pool to terminate: for example, reducing wokerCount or SHUTDOWN state to remove tasks from the queue.
final void tryTerminate() { for (;;) { int c = ctl.get(); // The following states are returned directly: // 1.Thread pool is still in RUNNING state // 2.SHUTDOWN Status but task queue is not empty // 3.runState >= TIDYING Thread pool has stopped or is stopping if (isRunning(c) || runStateAtLeast(c, TIDYING) || (runStateOf(c) == SHUTDOWN && !workQueue.isEmpty())) return; // The following logic can only continue if the thread pool is terminated. // 1.SHUTDOWN Status, at which point new tasks are no longer accepted and the task queue is empty // 2.STOP State, when called shutdownNow Method // workerCount Thread pool cannot be stopped without 0,And then the thread is idle waiting // You need to interrupt the thread to wake it up to continue processing shutdown Signal. if (workerCountOf(c) != 0) { // Eligible to terminate // runWoker Method w.unlock So that it can be interrupted,getTask The method also handles interrupts. // ONLY_ONE:Only one thread needs to be interrupted to process shutdown The signal is OK. interruptIdleWorkers(ONLY_ONE); return; } final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // Get into TIDYING state if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) { try { // Subclass overload: some resource cleanup terminated(); } finally { // TERMINATED state ctl.set(ctlOf(TERMINATED, 0)); // Continue awaitTermination termination.signalAll(); } return; } } finally { mainLock.unlock(); } // else retry on failed CAS } }
shutdown and shutdownNow
The shutdown method places the runState as SHUTDOWN and terminates all idle threads.
public void shutdown() { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); // Thread pool state set to SHUTDOWN,Return directly if it is already at least this state advanceRunState(SHUTDOWN); // Notice here that all idle threads are interrupted: runWorker The waiting thread was interrupted → Get into processWorkerExit → // tryTerminate The method ensures that the remaining tasks in the queue are executed. interruptIdleWorkers(); onShutdown(); // hook for ScheduledThreadPoolExecutor } finally { mainLock.unlock(); } tryTerminate(); }
The shutdownNow method sets the runState to STOP.Unlike the shutdown method, this method terminates all threads.
public List<Runnable> shutdownNow() { List<Runnable> tasks; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { checkShutdownAccess(); // STOP Status: New tasks are no longer accepted and tasks in the queue are no longer executed. advanceRunState(STOP); // Interrupt all threads interruptWorkers(); // Return to a task that has not yet been executed in the queue. tasks = drainQueue(); } finally { mainLock.unlock(); } tryTerminate(); return tasks; }
The main difference is that shutdown calls the interruptIdleWorkers method, while shutdownNow actually calls the interruptIfStarted method of the Worker class:
private void interruptIdleWorkers(boolean onlyOne) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { for (Worker w : workers) { Thread t = w.thread; // w.tryLock A lock can be acquired indicating that the thread is not running because runWorker Execute tasks first in lock, // So the interrupt is guaranteed to be an idle thread. if (!t.isInterrupted() && w.tryLock()) { try { t.interrupt(); } catch (SecurityException ignore) { } finally { w.unlock(); } } if (onlyOne) break; } } finally { mainLock.unlock(); } }
void interruptIfStarted() { Thread t; // At Initialization state == -1 if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { try { t.interrupt(); } catch (SecurityException ignore) { } } }
This is the primary role of the Woker class mentioned earlier in implementing AQS.
Note: The shutdown method may be implicitly called in finalize.
This blog is basically code and comments, so it would look boring if you weren't analyzing the ThreadPoolExecutor source code.
Reprinted at: https://my.oschina.net/u/1052976/blog/550068