Exploration of ThreadPool Executor Principle in Java Concurrent Package

Keywords: PHP Java less

Introduction to Thread Pool

The use of thread pools mainly solves two problems: 1. When a large number of asynchronous tasks are executed, thread pools can provide better performance. Without thread pools, when an asynchronous task needs to be executed, the creation and destruction of threads requires overhead. Threads in the thread pool are reusable and do not need to re-create and destroy threads every time an asynchronous task is performed. Second, the thread pool provides a means of resource limitation and management, such as restricting the number of threads, dynamically adding new threads and so on.

2. ThreadPoolExecutor class

1. Let's take a brief look at some member variables about ThreadPool Executor and what they mean.

ThreadPool Executor inherits AbstractExecutor Service, where the member variable ctl is an Integer-type atomic variable that records the state of the thread pool and the number of threads in the thread pool, similar to the one mentioned earlier. Read-write lock Use a variable to store two kinds of information. Here (Integer as 32 bits) the top three bits of the ctl represent the state of the thread pool, and the last 29 bits represent the number of threads in the thread pool. The following is a member variable in the ThreadPoolExecutor source code

 1 //(High 3 bits) indicates the state of the thread pool and low 29 bits indicate the number of threads in the thread pool.
 2 // The default state is RUNNING,Number of threads in thread pool is 0
 3 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
 4 
 5 //Represent specific platform Integer Binary digits-3 The number represented by the remaining digits is the number of threads;
 6 //among Integer.SIZE=32,-3 Later, the lower 29 bits represent the number of threads.
 7 private static final int COUNT_BITS = Integer.SIZE - 3;
 8 
 9 //Maximum number of threads (low 29 bits) 00011111111111111111111111111 (1<<29-1)
10 private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
11 
12 //Thread pool status (3 bits high indicates thread pool status)
13 //111 00000000000000000000000000000
14 private static final int RUNNING    = -1 << COUNT_BITS;
15 
16 //000 00000000000000000000000000000
17 private static final int SHUTDOWN   =  0 << COUNT_BITS;
18 
19 //001 00000000000000000000000000000
20 private static final int STOP       =  1 << COUNT_BITS;
21 
22 //010 00000000000000000000000000000
23 private static final int TIDYING    =  2 << COUNT_BITS;
24 
25 //011 00000000000000000000000000000
26 private static final int TERMINATED =  3 << COUNT_BITS;
27 
28 //Get high 3 bits (running status)==> c & 11100000000000000000000000000000
29 private static int runStateOf(int c)     { return c & ~CAPACITY; }
30 
31 //Get low 29 bits (number of threads)==> c &  00011111111111111111111111111111
32 private static int workerCountOf(int c)  { return c & CAPACITY; }
33 
34 //Computing atomic variables ctl New values (running status and number of threads)
35 private static int ctlOf(int rs, int wc) { return rs | wc; }

Let's briefly explain what the thread state above means:

(1) RUNNING: Accept new tasks and handle tasks in blocking queues

SHUTDOWN: Deny new tasks but handle tasks in blocked queues

(3) STOP: Deny new tasks and discard tasks in the blocking queue while interrupting tasks currently being performed

TIDYING: The number of active threads in the current thread pool is 0 after all tasks are executed (including those in the blocking queue), and terminated method will be called.

TERMINATED: Termination status. State after terminated method call

2. The following is a preliminary understanding of ThreadPool Executor's parameters and implementation principles

corePoolSize: Number of Core Current Vehicles in Thread Pool

(2) workQueue: A blocking queue used to save tasks waiting for task execution (such as Array Blocking Queue based on arrays, Linked Blocking Queue based on linked lists, etc.)

Maximum PoolSize: Maximum number of threads in the thread pool

ThreadFactory: a factory for creating threads

Rejected Execution Handler: Rejected Execution Handler: Rejection Policy, which means that when the queue is full and the number of threads reaches the maximum number of threads in the thread pool, there are four main strategies for newly submitted tasks: AbortPolicy (throwing an exception), Caller RunsPolicy (running the task only with the thread of the caller), Discard Oldest Policy (losing the latest in the blocked queue) DiscardPolicy (discarded without processing)

keepAliveTime: Survival time. If the number of threads in the current thread pool is larger than the number of core threads and the current thread is idle, this variable is the maximum lifetime of these threads.

TimeUnit: Time unit of survival time.

According to the above parameter introduction, a brief understanding of the thread pool implementation principle, to submit a new task as the starting point, analysis of the main thread pool processing process

3. About the use types of some thread pools

(1) New Fixed ThreadPool: Create a pool of threads with nThreads as the number of core threads and the maximum number of threads, and the length of blocking queue is Integer.MAX_VALUE. Keeping AliveTime = 0 means that as long as the number of threads is more than the number of core threads and the current idle thread is recycled.

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

 

(2) New Single ThreadExecutor: Create a pool of threads with 1 core threads and 1 maximum threads, and the length of the blocking queue is Integer.MAX_VALUE. Keeping AliveTime = 0 indicates that the thread is recycled as long as the number of threads is larger than the number of core threads and the current thread is idle.

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

 

(3) New Cached ThreadPool Executor: Create a pool of threads created on demand with 0 initial threads and Integer.MAX_VALUE maximum threads, and the blocking queue is listed as a synchronous queue (with only one element at most). Keeping AliveTime = 60 indicates that the current thread is recycled as long as it is idle within 60 seconds. This type of thread pool is characterized by the fact that tasks joining the synchronization queue are executed immediately and that there is at most one task in the synchronization queue.

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

 

4. Other members in ThreadPool Executor

The ReentrantLock can be referenced from the previous one. Locks in Java -- Lock and synchronized Among them, the specific implementation principle of ReentrantLock is reduced.

Refer to the previous section on AQS. Queue Synchronizer AQS in Java It also talks about the concrete realization principle analysis of AQS.

Refer to the previous article for knowledge about conditional queues Condition of Thread Cooperation in Java It talks about the implementation principle of thread collaboration Condition in Java.

//Exclusive locks to control new worker threads Worker Atomicity of operation
private final ReentrantLock mainLock = new ReentrantLock();

//A collection of worker threads, Worker Inherited AQS Interfaces and Runnable Interface is the thread object that handles the task concretely
//Worker Realization AQS,It also implements a simple non-reentrant exclusive lock, in which state=0 Indicates that the current lock has not been acquired. state=1 Represents that the lock is acquired,
//state=-1 Express Work Default state at creation time and settings at creation time state=-1 To prevent runWorker Method was interrupted before running
private final HashSet<Worker> workers = new HashSet<Worker>();

//termination Is the conditional queue corresponding to the lock, invoked by the thread awaitTermination Time to store blocked threads
private final Condition termination = mainLock.newCondition();

3. Source code analysis

1. Implementation of public void execute(Runnable command) method

The function of the executor method is to submit task command to thread pool execution, which can be easily understood in the following figure. The implementation of ThreadPool Executor is similar to a producer-consumer model. When users add tasks to the thread pool, it is equivalent to producer production elements, and the worker worker worker thread executes tasks directly or obtains tasks from the task queue, which is equivalent to elimination. Consumption element.

 1 public void execute(Runnable command) {
 2     //(1)First check if the task is null,by null Throw an exception, otherwise proceed to the following steps
 3     if (command == null)
 4         throw new NullPointerException();
 5     //(2)ctl The value contains the status of the current thread pool and the number of threads in the thread pool
 6     int c = ctl.get();
 7     //(3)workerCountOf The method is to get the number of threads in the current thread pool, if less than 29 bits. corePoolSize,Open a new thread to run
 8     if (workerCountOf(c) < corePoolSize) {
 9         if (addWorker(command, true))
10             return;
11         c = ctl.get();
12     }
13     //(4)If thread pool processing RUNNING State, add tasks to the blocking queue
14     if (isRunning(c) && workQueue.offer(command)) {
15         //(4-1)Second inspection, acquisition ctl value
16         int recheck = ctl.get();
17         //(4-2)If the current thread pool is not derived from RUNNING Status, delete tasks from the queue, and execute a rejection policy
18         if (! isRunning(recheck) && remove(command))
19             reject(command);
20         //(4-3)Otherwise, if the thread pool is empty, add a thread
21         else if (workerCountOf(recheck) == 0)
22             addWorker(null, false);
23     }
24     //(5)If the queue is full, the new thread is added, and if the new thread fails, the rejection policy is executed.
25     else if (!addWorker(command, false))
26         reject(command);
27 }

Let's take a look at the execution process of the above code and analyze it according to the number of tags:

Step (3) Determines whether the number of threads in the current thread pool is less than corePoolSize, and if it is less than the number of core threads, a new core thread will be added to the workers to perform tasks.

If the number of threads in the current thread pool is greater than the number of core threads, it executes (4). (4) First, determine whether the current thread pool is in the RUNNING state. If it is in that state, add tasks to the task queue. Here, we need to determine the status of the thread pool because it may already be in the non-RUNNING state, while in the non-RUNNING state, we need to discard new tasks.

If you want to add tasks to the task queue successfully, you need a second check, because after adding tasks to the task queue, the state of the thread pool may change, so you need a second check. If the current thread pool is no longer RUNNING state, you need to remove the task from the task queue, and then execute the rejection policy; if the second check passes, Execute 4-3 code to re-judge whether the current thread pool is empty or not. If the thread pool is empty and there are no threads, a new thread needs to be created.

If the above step (4) fails to create an add task, indicating that the queue is full, then (5) attempts to reopen new thread execution tasks (analogous to thread3 and thread4 in the figure above, i.e. threads that are not core threads), if the number of threads in the current thread pool is greater than the maximum number of threads, it means that new threads cannot be opened. This is where the thread pool is full and the task queue is full, and the rejection strategy needs to be implemented.

Posted by brauchii on Tue, 11 Jun 2019 10:48:20 -0700