This is the 13th day of my participation in the August More Text Challenge. For details, see:August is more challenging
preface
Thread pools are the most widely used concurrency framework in Java and can be used by almost any program that needs to execute tasks asynchronously or concurrently. There are three benefits to using thread pools properly during development.
-
- Reduce resource consumption
-
- Increase response speed
-
- Improved thread manageability
Thread pools are a common topic in interviews. This article will talk about how thread pools work and how they can be used, and the difference between JDK thread pools and Spring thread pools.
1. Basic principles of thread pools
Compared with some complex business scenarios in our daily development, the process of thread pool is not complicated. The process is as follows:
2. Thread pool Executor framework
The Executor interface is the most basic part of the thread pool framework and defines an execute method for executing Runnable.
An important subinterface under Executor, ExecutorService, defines the specific behavior of the thread pool
- Execute (Runnable) : Executes a Runable task
- Submit can be used to submit a Callable or Runnable task and return a Future object that represents the task
- Shutdown Closes the submitted tasks and does not take over new tasks
- ShutdownNow stops all tasks being performed and closes the business
- IsTerminated tests whether all tasks have been performed
- IsShutdown Tests whether the ExecutorService has been shut down
Let’s look at creating a thread pool with ThreadPoolExecutor
2.1 ThreadPoolExecutor thread pool framework
Thread pool property
CTL is a field that controls the running state of the thread pool and the number of valid threads in the thread pool.
It contains two parts of information: the running state of the thread pool (runState) and the number of valid threads in the thread pool (workerCount).
As you can see here, the Integer type is used to hold the runState for the high 3 bits and the workerCount for the low 29 bits. COUNT_BITS is 29, CAPACITY is 1 moved 29 bits to the left minus 1 (29 ones), this constant represents the upper limit of workerCount, which is about 500 million.
Ctl-related methods
- RunStateOf: Gets the running state.
- WorkerCountOf: Gets the number of active threads;
- CtlOf: Gets the value of the health status and the number of active threads.
2.2 Thread pool status
The thread pool has five states
- RUNNING
- SHUTDOWN
- STOP
- TIDYING
- TERMINATED
2.2.1 RUNNING
(1) Status description: When the thread pool is in the RUNNING state, it can receive new tasks and process the added tasks. (2) State switch: The initialization state of the thread pool is RUNNING. In other words, once the thread pool is created, it is in the RUNNING state and the number of tasks in the thread pool is zero!
2.2.2 SHUTDOWN
(1) Status description: When the thread pool is in SHUTDOWN state, it does not receive new tasks, but can process added tasks. (2) State switch: When the shutdown interface of the thread pool is called, the thread pool changes from RUNNING -> shutdown.
Then STOP
(1) Status description: When the thread pool is in the STOP state, it does not receive new tasks, does not process the added tasks, and will interrupt the tasks being processed. (2) State switch: when calling the thread pool shutdownNow() interface, the thread pool from (RUNNING or SHUTDOWN) -> STOP
2.3.4 TIDYING
(1) Status description: When all tasks are terminated, the “number of tasks” recorded by CTL is 0, and the thread pool will become TIDYING. The hook function terminated() is executed when the thread pool enters the TIDYING state. Terminated () is empty in the ThreadPoolExecutor class if the user wants to process the thread pool if it becomes TIDYING; This can be done by overloading the terminated() function.
(2) State switch: When the thread pool is in SHUTDOWN state, the blocking queue is empty and the task executed in the thread pool is empty, the thread pool will change from SHUTDOWN -> TIDYING. When the thread pool is in the STOP state and the task executing in the thread pool is empty, the thread pool will STOP -> TIDYING.
2.3.5 TERMINATED
The thread pool is TERMINATED and is in the TERMINATED state.
(2) State switch: the thread pool in the TIDYING state will be terminated by TIDYING -> terminated after execution.
The conditions for entry to TERMINATED are as follows:
- The thread pool is not in the RUNNING state.
- The thread pool state is not TIDYING or TERMINATED.
- If the thread pool status is SHUTDOWN and the workerQueue is empty;
- WorkerCount 0;
- Setting the TIDYING state succeeded. Procedure
2.3 Implementation of thread pool
As you can see from the UML diagram above, there are two implementations of JDK thread pools
- ThreadPoolExecutor Specifies the default thread pool
- ScheduledThreadPoolExecutor thread pool regularly
ThreadPoolExecutor Creates a thread pool
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
Copy the code
The important parameters
2.3.1 corePoolSize
The number of core threads in the thread pool. When a task is submitted, the thread pool creates a new thread to execute the task until the current number of threads equals the corePoolSize. If the current number of threads is corePoolSize, the tasks that continue to be submitted are saved to the blocking queue and waiting to be executed.
If the pool’s prestartAllCoreThreads() method is executed, the pool is created and starts all core threads in advance.
2.3.2 maximumPoolSize
The maximum number of threads allowed in a thread pool. If the current blocking queue is full and the task continues to be submitted, a new thread is created to execute the task, provided that the current number of threads is less than maximumPoolSize;
2.3.3 keepAliveTime
The thread pool maintains the idle time allowed by a thread. When the number of threads in the pool is larger than the corePoolSize, if no new tasks are submitted, the threads outside the core will not be destroyed immediately, but will wait until their keepAliveTime exceeds.
2.3.4 unit
KeepAliveTime unit;
2.3.5 workQueue
The blocking queue is used to hold tasks waiting to be executed, and the tasks must implement the Runable interface. The following blocking queue is provided in the JDK:
- ArrayBlockingQueue: Bounded blocking queue based on array structure, sorting tasks by FIFO
- LinkedBlockingQuene: Blocking queue based on linked list structure, sorting tasks by FIFO, throughput is usually higher than ArrayBlockingQuene;
- SynchronousQuene: a blocking queue that does not store elements. Each insert operation must wait until another thread calls the remove operation, otherwise the insert operation will remain blocked, usually with high throughput.
- PriorityBlockingQuene: Unbounded blocking queue with priority;
- threadFactory
It is a variable of type ThreadFactory that is used to create a new thread. Default Executors. DefaultThreadFactory () to create a thread. When a thread is created using the default ThreadFactory, the new thread is created with the same NORM_PRIORITY priority and is non-daemon, with the thread name also set.
2.3.6 handler
When the blocking queue is full and there are no idle worker threads, one policy must be adopted to process the task if it continues to submit. The thread pool provides four policies:
- AbortPolicy: Throws an exception directly, the default policy
- CallerRunsPolicy: Executes the task with the caller’s thread
- DiscardOldestPolicy: Discards the first blocking task in the queue and executes the current task
- DiscardPolicy: Discards the task directly
All four of the above policies are inner classes of ThreadPoolExecutor. Of course, RejectedExecutionHandler can also be implemented according to the application scenario, such as logging or persistent storage can not handle the saturation policy.
3. Configure the thread pool
To properly configure a thread pool, you must first analyze the task characteristics
- The nature of the task: CPU intensive, IO intensive, and hybrid.
- Priority of tasks: high, medium and low.
- Task execution time: long, medium and short.
- Task dependencies: Whether to rely on other system resources, such as database connections.
4. Monitor thread pools
If thread pools are used extensively in your system, it is necessary to monitor the thread pools so that when problems occur, they can be quickly identified based on the usage of the thread pools. This can be monitored using the parameters provided by the thread pool. The following properties can be used when monitoring the thread pool.
- TaskCount: indicates the number of tasks to be executed by the thread pool.
- CompletedTaskCount: The number of tasks that have been completed during the run of the thread pool, less than or equal to taskCount.
- LargestPoolSize: Maximum number of threads that have ever been created in the thread pool. This data lets you know if the thread pool has ever been full.
- GetPoolSize: Indicates the number of threads in the thread pool. Threads in the pool do not self-destruct if the pool is not destroyed, so the size only increases.
- ·getActiveCount: Gets the number of active threads.
The last
There are many contents of thread pool, which may need to be divided into several series to introduce. This article mainly introduces the principle of thread pool, some core parameters, methods and the overall framework. The following will analyze thread pool from the source code and actual business scenarios.
Reference documentation
Implementation Principle of Java Thread Pool and Its Practice in Meituan Business