In Article How to implement threads, source code analysis: Runnable, Thread, Callable, Future, FutureTask We know that there are three ways to implement threads. When threads are needed, threads can be created to execute tasks in these ways. If many tasks need to be executed at the same time, threads can be created to execute. However, the creation and destruction of threads is a time-consuming operation. If threads are created and destroyed frequently, the efficiency of the system will be affected. At this point, we can use the thread pool to perform tasks, that is to say, we can create several threads in advance, and if a task comes, we can take one of them to execute it. When the thread finishes the task, it does not destroy, but remains to perform the next task, so that we do not need to create and destroy threads frequently. In Java, ThreadPoolExecutor class is provided to implement the related operations of thread pool. Here's an analysis of the implementation principle of ThreadPoolExecutor class.
1: class diagram
If you want to see the source code of a class, first of all, you need to look at its class diagram, clarify its inheritance relationship, so that you may have an impression in your mind. The class diagram of ThreadPoolExecutor is as follows:
ExecutorÂ
The top-level interface implemented by ThreadPoolExecutor is Executor interface, which defines only one method, execute(), to execute threads. Note that this method has no return value and may throw Rejected Execution Exception and NullPointerException exceptions.
public interface Executor { /** * @param command the runnable task * @throws RejectedExecutionException if this task cannot be accepted for execution * @throws NullPointerException if command is null */ void execute(Runnable command); }
ExecutorService
ExecutorService inherits Executor, which is also an interface that provides the following methods.
public interface ExecutorService extends Executor { //Close the thread pool and the previously submitted tasks will be executed, but no new tasks will be accepted. If it is closed, the call again has no other effect. //This method does not wait for previously submitted tasks to complete. void shutdown(); //Trying to stop all tasks that are being performed will not process the tasks that are waiting and return to the list of tasks that are waiting to be performed. List<Runnable> shutdownNow(); //Is it closed? boolean isShutdown(); //If all tasks are completed after shutdown is invoked, true is returned. //It is worth noting that isTerminated will never be true unless shutdown or shutdown Now is called first. boolean isTerminated(); //When shutdown is invoked, it blocks until all tasks are computed, or the timeout or the thread is interrupted. boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException; //Submit the Callable task to the thread pool and return the result of the task <T> Future<T> submit(Callable<T> task); //Submit the Runnable task to the thread pool and return the result <T> Future<T> submit(Runnable task, T result); /Submission Runnable Tasks are executed in the thread pool and results are returned/ Future<?> submit(Runnable task); //Execute all tasks and return the result set <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException; //Execute all tasks, return result set, timeout <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException; //Execute any task in the task set <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException; //Execution of any task in the task set, with timeouts <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
ExecutorService also provides the submit() method for submitting tasks, which differs from the execute() method in that submit() can have a return value, while the execute() method has no return value.
AbstractExecutorService
AbstractExecutorService implements the ExecutorService interface and provides default implementations of some methods, as well as some additional methods:
public abstract class AbstractExecutorService implements ExecutorService { protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { return new FutureTask<T>(runnable, value); } protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { return new FutureTask<T>(callable); } public Future<?> submit(Runnable task) { if (task == null) throw new NullPointerException(); RunnableFuture<Void> ftask = newTaskFor(task, null); execute(ftask); return ftask; } public <T> Future<T> submit(Runnable task, T result) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task, result); execute(ftask); return ftask; } public <T> Future<T> submit(Callable<T> task) { if (task == null) throw new NullPointerException(); RunnableFuture<T> ftask = newTaskFor(task); execute(ftask); return ftask; }
ThreadPoolExecutor
Next, look at ThreadPoolExecutor, which is a major class of thread pool implementations.
2: The state of thread pool
In the ThreadPoolExecutor class, several variables are provided to represent the state of the thread pool:
private static final int COUNT_BITS = Integer.SIZE - 3; private static final int RUNNING = -1 << COUNT_BITS; // -536870912 private static final int SHUTDOWN = 0 << COUNT_BITS; // 0 private static final int STOP = 1 << COUNT_BITS; // 536870912 private static final int TIDYING = 2 << COUNT_BITS; // 1073741824 private static final int TERMINATED = 3 << COUNT_BITS; // 1610612736
These states are mainly represented by runState. Look at what these states mean.
RUNNING: Represents that the thread pool can accept new tasks and process queued tasks
SHUTDOWN: Do not accept new tasks, but process queued tasks
STOP: No new tasks, no queuing tasks
TIDYING: All tasks are terminated, workerCount is zero, and the thread that transitions to state TIDYING runs the terminate () hook method
TERMINATED: terminate() method completed
The transitions between these five states are as follows:
When the shutdown() method is called, the state changes from RUNNING to SHUTDOWN, which may also occur in the finalize() method.
When the shutdownNow() method is called, the state changes from RUNNING or SHUTDOWN to STOP.
When the tasks in the queue and thread pool are empty, the state changes from SHUTDOWN to TIDYING
When there are no tasks in the queue, the state changes from STOP state to TIDYING state
When the terminate() method has been completed, the state changes from TIDYING to TERMINATED
The conversion diagram is as follows:
3. Construction method
Next, look at some of the attributes of ThreadPoolExecutor:
// workQueue is mainly used to store tasks, any BlockingQueue can be used to save tasks; if the current number of threads is less than the corePoolSize core // If the number of threads currently running is greater than or equal to the size of core PoolSize core thread pool, // Submitted tasks are placed in a queue; there are generally three types of selection queues: // 1: SynchronousQueue // 2: LinkedBlockingQueue // 3: ArrayBlockingQueue private final BlockingQueue<Runnable> workQueue; // lock private final ReentrantLock mainLock = new ReentrantLock(); // Work set, accessible only by acquiring locks private final HashSet<Worker> workers = new HashSet<Worker>(); // Maximum number of threads ever appeared in the thread pool private int largestPoolSize; // Number of tasks completed private long completedTaskCount; // Thread-creating factories, if not provided, default is to use the default factories, they belong to a thread group, the same priority, etc., can be customized. private volatile ThreadFactory threadFactory; // Rejection policy: How will the submission task be handled when the thread pool is closed, or when the queue is full and the number of threads has reached the maximum number allowed by the thread pool? // The ThreadPoolExecutor class provides four internal classes for processing: // 1: AbortPolicy: This policy throws an exception. (default) // 2: CallerRunsPolicy: It runs the rejected task directly in the calling thread of the execute method. If the thread pool is closed, the task will be discarded. // DiscardPolicy: Discarding Tasks Directly // 4: Discard Oldest Policy: Discard the earliest unprocessed task and execute it private volatile RejectedExecutionHandler handler; // Thread lifetime, when the number of threads in the thread pool is greater than corePoolSize and no longer perform tasks, the maximum inventory time is destroyed when it exceeds time. private volatile long keepAliveTime; // If false (default), the core thread will remain active even if it is idle. // If true, the core thread uses keepAliveTime to wait for work over time. private volatile boolean allowCoreThreadTimeOut; // The size of the core thread pool, which will never be destroyed unless allowCoreThreadTimeOut is set private volatile int corePoolSize; // Maximum Thread Pool Size private volatile int maximumPoolSize;
After understanding the above definition of attributes, let's look at the construction method of ThreadPool Executor, which provides four construction methods:
Let's look at the definition of the fourth construct. The other three call the fourth construct and some parameters provide default values.
public ThreadPoolExecutor(int corePoolSize, // Core thread pool size int maximumPoolSize, // Maximum thread pool size long keepAliveTime, // Thread lifetime TimeUnit unit, // Life preservation time unit BlockingQueue<Runnable> workQueue, // Cache queues for storing tasks ThreadFactory threadFactory, // Thread factory RejectedExecutionHandler handler) // Refusal strategy { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
The constructed parameters are the attributes mentioned above, and the functions of each parameter are explained above.
When a task is submitted to the thread pool, how will the task be executed? Next, let's look at how to execute the task after it has been submitted. After the task is submitted to the thread pool, it will be executed by the execute() method. Although submit() can also submit the task to the thread pool for execution, during the resolution of the AbstractExecutorService class above, submit will also call execute() to perform the task. Next, look at a flow of the execute() method:
// To execute tasks, tasks may create new threads to execute, or they may take out existing threads from the thread pool to execute; if the thread pool is closed or the cache queue is full, and the number of threads in the thread pool reaches the maximum allowable value, // Submitted tasks will be handled by Rejected Execution Handler // There are three steps to submit a task: // 1: If the number of threads running in the thread pool is less than corePoolSize, a new thread is created to perform the task. // 2: If the current number of worker threads is greater than or equal to corePoolSize, the task will be inserted into the cache queue. If the task inserts into the queue successfully, it still needs to check again whether the task should be added. If the thread pool has been closed, etc. // 3: If the queue is full and adding tasks to the queue fails, a new thread is attempted to execute the task. If the number of threads in the thread pool has reached maximum PoolSize, it will be processed by Rejected Execution Handler. public void execute(Runnable command) { if (command == null) throw new NullPointerException(); // The state of the thread pool is mainly controlled by ctl, which records runState and workCount int c = ctl.get(); // If the current number of worker threads is less than corePoolSize, a new thread is created to perform the task and put the thread into the thread pool. if (workerCountOf(c) < corePoolSize) { // Adding tasks, the second parameter indicates whether to limit the number of adding threads based on corePoolSize or maximumPoolSize. // If true, judge by corePoolSize; if false, judge by maximumPoolSize if (addWorker(command, true)) return; // Failed to add and retrieve status c = ctl.get(); } // If the current thread pool state is running and the task is added to the queue successfully if (isRunning(c) && workQueue.offer(command)) { // Get the state of the thread pool again int recheck = ctl.get(); // If the state of the thread pool is not running, the task needs to be deleted because it has been added to the queue; then the task is processed by Rejected Execution Handler if (! isRunning(recheck) && remove(command)) reject(command); // If the thread pool is still running and the number of threads working is 0, add tasks. // The second parameter is false, setting the upper limit of the limited number of threads in the thread pool to maximumPoolSize else if (workerCountOf(recheck) == 0) addWorker(null, false); } //If implemented here, there are two situations: // 1. Thread pool is no longer running state; // 2. Thread pool is running, but workerCount >= corePoolSize and workQueue is full, then the addWorker method is called again. // But the second parameter is passed in false, setting the upper limit of the limited number of threads in the thread pool to maximumPoolSize; if it fails, the task is handled by Rejected Execution Handler else if (!addWorker(command, false)) reject(command); }
The process of adding tasks mentioned above is summarized as follows:
1. If the number of threads in the current thread pool is less than corePoolSize, new threads are created to perform tasks
2. If the number of threads in the current thread pool is greater than or equal to corePoolSize and the cache queue is not full, insert the task into the queue
3. If the number of threads in the current thread pool is greater than or equal to corePoolSize and less than the maximum number allowed by the thread pool and the queue is not full, a new thread is created to perform the task.
4. If the number of threads in the current thread pool is greater than the maximum number allowed by the thread pool and the queue is full, there is a rejection policy.
In the ThreadPoolExecutor class, there is also an internal class Worker class, which is mainly used to maintain the thread interruption status of running tasks. It implements Runnable and AbstractQueuedSynchronizer, which means that it is also a thread. For the queue synchronizer AbstractQueuedSynchronizer, you can see the article. Lock Lock Lock Source Code Analysis
// Each thread in the thread pool is encapsulated as a Worker object, and ThreadPool maintains a set of Worker objects. private final class Worker extends AbstractQueuedSynchronizer implements Runnable { // The running thread can be null, that is, threads are threads created by ThreadFactory when the constructor is called, and threads are threads used to process tasks. final Thread thread; // To run the task for the first time, null, or first Task, can be used to save the incoming task Runnable firstTask; // Task counter per thread volatile long completedTasks; //Create worker Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; // Create threads using thread factories this.thread = getThreadFactory().newThread(this); } // Thread of operation public void run() { runWorker(this); } // Whether to acquire locks // 0 means no lock was acquired // 1 denotes acquisition of locks protected boolean isHeldExclusively() { return getState() != 0; } // Attempt to get the lock, get back true, get back false protected boolean tryAcquire(int unused) { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } // Unlock protected boolean tryRelease(int unused) { setExclusiveOwnerThread(null); setState(0); return true; } // Method of calling queue synchronizer 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 { t.interrupt(); } catch (SecurityException ignore) { } } } }
This is the ThreadPool Executor analysis:
Reference resources: Deep Understanding of Java Thread Pool Executor