stay Multithread - ThreadPool Executor ThreadPool Executor is introduced, including the whole process of thread pool execution. This article mainly explores how the thread pool executes tasks from the point of view of source code.
Execution steps
Let's review first. Multithread - ThreadPool Executor The steps mentioned in the thread pool to perform tasks are as follows:
Step 1: If the number of threads running is less than corePoolSize, create a thread to run the task immediately without adding the task to the task queue.
Step 2: If the number of threads running is greater than or equal to corePoolSize, inserting the task into the task queue will not execute the task immediately, waiting for the running thread to execute the current task idle, and then remove the task from the task queue to continue executing.
Step 3: If the task queue is full at this time and cannot continue to insert into the task queue, if the number of threads running at this time is less than maximumPoolSize, then a thread will be created to run the task.
Step 4: If the task queue is full and the number of threads running is greater than or equal to maximumPoolSize, the thread pool throws an exception, telling the caller, "I can't accept the task anymore" and handles it through exception handling handler.
Of course, the above steps are not complete steps. When the state of thread pool is found to be non-RUNNING state in the execution process, an exception is thrown, the task is rejected and handler is handled by exception handler.
Source code analysis
Executor Service and Executors
Create and submit a task to the thread pool for execution
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new TestRunnable());
ExecutorService is an interface. It inherits the Executor interface, which defines an executor(Runnable command)
public interface Executor {
void execute(Runnable command);
}
ExecutorService defines some thread pool-related operations such as shutdown(), isTerminated().
Next, let's look at Executors, a helper class that provides static methods such as thread pool creation.
//Create a thread pool with core threads equal to the maximum number of threads, equal to one task per submission, when less than the number of core threads
//A thread executes, or it waits for the core thread pool to be idle
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
//The number of core threads is 0 and the length of task queue is 0, which is equivalent to creating a thread to execute every task submitted.
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
//Both the core thread pool and the maximum number of threads are 1, which is equivalent to when a thread executes a task.
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
2.ThreadPoolExecutor
execute(Runnable command): Submit tasks to thread pool
As you can see from the above, calling Executors static method to create thread pool (other thread pools are not analyzed in this article), and its internal call to create ThreadPoolExecutor, so submitting a task to the thread pool is finally executed by ThreadPoolExecutor (Runnable command) method to analyze the code of execute(Runnable command).
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.
*
* 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.
*
* 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.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
//If the current number of worker threads is less than the number of core threads, a worker is created to perform the task.
//If the Creation Thread (Worker) succeeds, it returns
if (addWorker(command, true))
return;
c = ctl.get();
}
//If the current number of worker threads is greater than or equal to the number of core threads or if the worker created above fails
//Determines whether the thread pool is running, and if it is running, adds tasks to the task queue
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//Check again if the thread pool is running, (double-check)
//If it is not running, the newly added task is removed from the task queue and reject() is invoked to refuse to perform the task.
//If it is running, it detects whether the current number of worker threads is 0. If it is 0, it calls addWorker() to create a thread to execute the task.
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//If the thread pool is not running or if adding tasks to the task queue fails,
//Attempt to create a new thread to execute a task through addWorker. If a new thread fails to create, call reject() to refuse to execute the task.
else if (!addWorker(command, false))
reject(command);
}
The commentary mentions three processes:
1. When the number of worker threads in the thread pool is less than the number of core threads, thread execution is created through addWorker(), where addWorker() checks the status of the current thread pool and the number of worker thread pools. If the thread pool is not running or the number of worker threads is equal to or greater than the number of core threads during this period, false is returned to indicate failure, and then step 2 is executed.
2. When tasks are successfully added to the task queue, double-check is still needed to check whether the thread pool is shut down (non-running state) when executing to the current location, because the thread pool in shutdown state does not accept new tasks, so it is necessary to remove the tasks above from the queue; secondly, when executing here, it is necessary to check whether the thread pool is shutdown (non-running state). To check whether the number of worker threads in the current thread pool is 0, because the number of core threads is 0, it is possible that when the number of worker threads is zero, all the worker threads will be timed out and cause the thread to die, so a new thread execution needs to be created.
3. When a task fails to add to the queue, it is necessary to try to create a new thread to execute the task. If a new thread fails to create, the task will be rejected if the thread pool is not in a non-running state or the number of working threads has reached the maximum number.
AddWorker (Runnable first Task, Boolean core): Create threads and perform tasks
//Core indicates whether core threads are created or not
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
//Get the number of thread pool worker threads and thread pool status
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
//This can be simplified as follows:
//rs > SHUTDOWN || ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())
//There are two situations in which tasks are refused:
//1. Thread pool is in any state of STOP, TIDYING, or TERMINATED
//2. When the thread pool is in SHUTDOWN, if firstTask is not null or workQueue is empty, it refuses to execute.
//That is, only if firstTask is not null and workQueue is not empty can it not return and can continue to execute below.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
//If the current number of worker threads is larger than the capacity or
//If the core thread is created, if the current worker thread is greater than or equal to the number of core threads or
//If a normal thread is created, if the current worker thread is greater than the maximum number of threads
//Refuse to perform this task
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//CAS operation, thread safety, +1 success, that can create threads, jump out of the loop
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
//Check again if the thread pool status has changed and if it has changed, loop again
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
//Execute here to say you can create new threads
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//Create new threads through Worker
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
//Thread Creation Successful
//Re-lock, thread synchronization
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
//Here rs== SHUTDOWN & & firstTask=== null appears again, and here's why.
//Create thread
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);//Add the created worker to the workthread queue
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
//Identify the successful creation of workthreads
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
//Start the created workthread
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
Through the above analysis, there is one point that needs special attention. (rs = SHUTDOWN & firstTask = null & & & workQueue. isEmpty ()) This judgment, as explained in the note above, is that when the thread pool state is SHUTDOWN, it can continue to execute only when the first Task = null and workQueue is not empty, otherwise the above condition is valid, and direct return is false. Remember the last article Multithread - ThreadPool Executor This paper introduces that when the thread pool is SHUTDOWN, new tasks will not be accepted, but all tasks in the task queue will be executed. If the thread pool is in SHUTDOWN state and the first Task is not null, then the newly added tasks will be refused to execute. When the first Task is null, the workQueue will return and will not continue to execute because the workQueue is empty. And you can't add new tasks, and there's no need to continue creating threads, so there's! (rs = SHUTDOWN & firstTask = null & & & workQueue. isEmpty ()) judgment. After creating the thread later, there is a judgment that RS < SHUTDOWN | (rs = SHUTDOWN & & firstTask = null). When the state of the thread pool is SHUTDOWN, only first Task = null can add the created worker thread to the worker thread queue, and then the creation of the thread is really completed.
So when first Task = null? Remember that in execute(Runnable command), when a task is added to a queue, if the current number of Worker threads is judged to be zero, addWorker(null, false) is called; a thread is created; how does the Worker work internally?
Worker: Work threads
First look at Worker's code, and then post the core code for analysis.
//Implementation of Runnable Interface
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
//Create a thread through the thread factory and pass it in as a parameter
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
//Call runWorker
public void run() {
runWorker(this);
}
}
Through the analysis of addWorker() just above, the start() of the worker.thread thread is finally called. As can be seen from the code of the Worker above, the final execution of start() is runWorker(this);
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
//Get the task passed in when creating Worker
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//When the task task is empty, the task is retrieved through getTask().
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
//When the state of the thread pool is at least STOP, interrupt all threads
//Since the status is STOP, tasks in the task queue are no longer processed
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
//Task commencement
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 {
processWorkerExit(w, completedAbruptly);
}
}
When the incoming task is null when the Worker is created, the task is retrieved through getTask(). When getTask() is null, the thread execution terminates without satisfying the while loop.
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
//This judgment can be simplified if (rs > SHUTDOWN | (rs = SHUTDOWN & workQueue. isEmpty ())
//1. When the thread pool state is SHUTDOWN and the task queue is empty
//2. When the thread pool state is STOP or above,
//Work threads minus 1, return null, thread exit
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
//If no core thread timeout is set, the task timeout will be read only when the current number of working threads is larger than the number of core threads.
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//1. When the number of working threads is larger than the maximum number of threads, the number of working threads is reduced immediately, and the threads are quitted without task reading.
//2. When the number of worker threads is greater than the number of core threads, but less than or equal to the maximum number of worker threads, the task will be read, waiting for the read timeout.
//3. The current number of threads is greater than 1 or the task queue is empty, indicating that as long as the task queue is not empty, at least one thread should be kept.
//Reduce the number of worker threads by 1 and return null to cause the thread to exit
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//workQueue is a blocking queue, and the acquisition task timeout occurs only when the timeout needs to be judged, otherwise it will be blocked all the time.
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
//Successfully remove the task and return
if (r != null)
return r;
//Execution to this point indicates that the read task timed out.
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
From the above analysis, we can see that,
When RS >= SHUTDOWN & (rs >= STOP | workQueue. isEmpty ()) is established, null will be returned directly without task reading and thread will be exited; this is the case when the thread pool state is at least STOP or SHUTDOWN and workQueue is empty;
Boolean timed = allowCoreThreadTimeOut | | WC > corePoolSize; determine whether the timeout is timeout, if no core thread timeout is set, when the current number of working threads is larger than the number of core threads, the read task timeout will occur, otherwise the waiting will be blocked all the time;
(wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())
1. When the number of working threads is larger than the maximum number of threads, the number of working threads is reduced immediately, and the threads are quitted without task reading.
2. When the number of worker threads is greater than the number of core threads, but less than or equal to the maximum number of worker threads, the task will be read, waiting for the read timeout.
3. WC > 1 | | workQueue. isEmpty () The current number of threads is greater than 1 or the task queue is empty, indicating that as long as the task queue is not empty, the thread pool will retain at least one worker thread.
When firstTast is null, it reads tasks from the task queue through getTask(), so the task passed in in addWorker(null,false) is null, which is equivalent to creating a new thread to read tasks directly from the task queue.
summary
1.ExecutorService and Executor are interfaces. Eexcutors tool class provides static functions to construct ThreadPool Executor. The final thread pool is implemented by ThreadPool Executor.
2. Source code analysis shows that when the thread pool is in SHUTDOWN state, it will not accept new tasks, and will complete tasks in the task queue; when the state is at least STOP, it will neither accept new tasks nor perform tasks in the task queue;
3. By addWorker(null,false), a new thread can be created to perform tasks in the task queue, provided that the current number of threads is less than the maximum number of threads;
4. The waiting time of non-core threads in thread pool is realized by waiting while reading tasks in blocking queue.
5. When the task queue is not empty, the thread pool should keep at least one thread.