Thread pooling is essentially a pooling technique for pooling threads, similar to connection pooling such as Mysql object pool StringCache.

A number of benefits of pooling:

1. Reduce resource consumption. Reuse resources such as threads or connections, creating and destroying resources itself is wasteful

2. Improved response speed Because resources are already created, tasks are executed without going through the creation process

3, improve the controllability of resources we can do unified tuning monitoring

4. Provide stronger scaling features such as deferred timed thread pools

Executor Abstract tasks Submit tasks for execution

ExecutorService extended task execution capabilities can provide a way to gracefully or violently stop the production of futures for one or more asynchronous tasks

AbstractExecutorService Process to perform a task

The ThreadPoolExecutor implementation maintains its own lifecycle management threads and tasks

First, check the RUNNING status of the thread pool. If it is not RUNNING, reject it directly. Ensure that the thread pool executes tasks in the RUNNING state. 1 If workerCount < corePoolSize, a thread is created and started to execute the newly submitted task. 2 If workerCount >= corePoolSize and the blocking queue in the thread pool is not full, the task is added to the blocking queue. 3 If workerCount >= corePoolSize && workerCount < maximumPoolSize and the blocking queue in the thread pool is full, a thread is created and started to execute the newly submitted task. 4 If workerCount >= maximumPoolSize and the blocking queue in the thread pool is full, the task is processed according to the reject policy. The default handling method is to throw an exception directly.

In addition to the task execution process, we need to pay attention to the thread pool operation mechanism:

1 How does the thread pool maintain its state? 2 How do thread pools manage tasks? 3 How does a thread pool manage threads?

One interesting thing is that the workerCount and runState states in the thread pool are actually maintained in a AtonmicInteger variable

The running state of the thread pool (runState) and the number of valid threads in the thread pool (workerCount), with runState stored in the higher 3 bits and workerCount stored in the lower 29 bits

The blocking queue of the task buffer thread pool

The ArrayBlockingQueue implementation of the queue can be used to implement unfair patterns because the index of the array can be accessed quickly

LinkedBlockingQueue The default queue length implemented by the LinkedBlockingQueue is the maximum Integer that is at risk of running out of memory and is used by default to block queues

PriorityBlockingQueue The default natural order of unbounded queues that support the priority level of threads can also be specified using the CompareTo() method

DelayQueue implements PriorityBlockingQueue to delay fetching elements from the queue until the unbounded queue delay expires

SynchronousQueue A queue that does not store elements. Each PUT operation must wait for a take operation to support a fair or unfair lock

LinkedTransferQueue An unbounded queue composed of a linked list structure

LinkedBlockingDeque a two-way blocking queue with a linked list structure

Task rejection When the number of thread pools reaches maximumSize and there are no idle threads, the policy is rejected

The JDK provides four rejection strategies

1, ThreadPoolExecutor. AbortPolicy directly throw an exception

2, ThreadPoolExecutor. DiscardPolicy discard task directly

3, ThreadPoolExecutor. DiscardOldestPolicy discarding the oldest task

4, ThreadPoolExecutor. CallerRunsPolicy to submit task thread execution (to slow the rate of the producer)

Management of Worker threads

The Worker thread implements the Runnable interface and holds a thread, thread, that initializes the task firstTask

Reclaiming Worker threads

The life cycle of Worker threads needs to be managed, that is, they need to be reclaimed in addition to being created, which involves retrieving threads and synchronizing thread states with executing threads. When the thread runs, the Worker will hold an exclusive lock and implement AQS by itself.

The thread collection for the thread pool is a call to processWorkerExit

1. Record the number of completed tasks

2. The thread references the hash table that removes the worker

3. Maintenance of thread pool state

Worker thread increment

Threads are added through the addWorker method in the thread pool. The addWorker method takes two parameters: firstTask and core. The firstTask parameter is used to specify the firstTask to be executed by the new thread. This parameter can be null. If the core parameter is true, it will determine whether the number of active threads is less than corePoolSize before adding a thread. If the core parameter is false, it will determine whether the number of active threads is less than maximumPoolSize before adding a thread

Execution of Worker tasks

The run method in the Worker class calls the runWorker method to perform the task. The execution of the runWorker method:

1. The while loop keeps getting tasks through the getTask() method.

The getTask() method fetches the task from the blocking queue.

3. If the pool is stopping, make sure the current thread is interrupted, otherwise make sure it is not interrupted.

4. Perform tasks.

5. If getTask is null, break out of the loop and execute processWorkerExit() to destroy the thread.

Scenario analysis

1. High requirements on response time RT. Since RT is highly demanding and insensitive to throughput, queues should be used to cache tasks,corePoolSize and maxPoolSize to create as many threads as possible to execute tasks quickly

2. Rapid processing of batch tasks requires throughput. Set the queue to buffer concurrent tasks and adjust the appropriate corePoolSize to set the number of threads handling the task. Setting too many threads here can also cause frequent thread context switches, slow down the processing of tasks, and reduce throughput.

Parameter reference for thread pools

Theoretically, tasks can be DIVIDED into IO – intensive and CPU – intensive tasks.

CPU intensive response times are fast, the CPU is always running, and CPU utilization is high for this type of task. The number of threads is the number of CPU cores, so that all threads are busy as much as possible. The same number of cpus is used to minimize the overhead of thread switching

IO intensive It takes a long time to perform I/O operations because the CPU is idle and thus the CPU usage is low. The number of threads is twice the number of CPU cores. In this way, if the I/O operation takes a long time, other threads will use the CPU to improve the CPU utilization.

But the calculation here is not suitable for complex scenarios.

Dynamic thread pool parameters We need a way to adjust thread pool parameters without restarting. In this way, rapid adjustment can be achieved and the fault recovery time can be shortened. We can migrate thread pool parameters from code to a distributed configuration center. Implement dynamic configuration of thread pool parameters, effective immediately.

The core design of dynamic thread pools includes the following three aspects:

Simplify thread pool configuration: There are eight thread pool construction parameters, but the core three are corePoolSize, maximumPoolSize, and workQueue, which most determine the task allocation and thread allocation strategy of the thread pool. Considering that in practical application, there are mainly two scenarios for obtaining concurrency :(1) parallel execution of sub-tasks to improve response speed. In this case, a synchronous queue should be used, and no task should be cached, but should be executed immediately. (2) Perform a large number of tasks in parallel to improve throughput. In this case, bounded queues should be used. Queues should be used to buffer large numbers of tasks, and queue capacity must be declared to prevent unrestricted accumulation of tasks. Therefore, the thread pool only needs to provide the configuration of the three key parameters and the choice of two queues to meet most service requirements. Less is More. Parameters can be dynamically modified: in order to solve the problem of parameter mismatch and high cost of modifying parameters. On the basis of the high scalability of Java thread pool, encapsulate the thread pool, allowing the thread pool to listen for messages outside the synchronization, and modify the configuration according to the message. The thread pool configuration is placed on the platform side, allowing developers to easily view and modify the thread pool configuration. Add thread pool monitoring: Lack of observation of something’s state makes it impossible to improve. Add the ability to monitor the life cycle of thread pool tasks to help developers understand thread pool status.