Why do we need thread pools

In Java, multiple threads can be used for concurrent execution. However, if a large number of threads are created and destroyed in a short time, a large amount of system time will be occupied and system efficiency will be affected.

In order to solve the above problems, Java introduced a thread pool, which enables the created threads to be managed by the system within a specified period of time, rather than being created during execution and destroyed after execution, thus avoiding the system overhead caused by frequent creation and destruction of threads.

Thread pool how to use, as well as the implementation principle, processing steps, what to use matters needing attention, today mainly from these aspects in detail introduced Java thread pool.

Thread pool processing flow

Taking ThreadPoolExecutor as an example, when we assign a Runnable to a thread pool to execute, the thread pool processes something like this:

  • Determine if the core threads in the thread pool are free. If so, assign the new task to one of the idle threads. If there are no idle threads and the number of core threads in the current thread pool is less than corePoolSize, create another core thread.
  • If the number of threads in the thread pool has reached the core number and they are busy, the new task is put on the wait queue. If the wait queue is full again, check to see if the current number of threads has reached maximumPoolSize, and continue to create threads if not.
  • If so, it is left to the RejectedExecutionHandler(rejection policy) to decide how to handle the task.

Use of thread pools (ThreadPoolExecutor)

In Java, the concept of a thread pool is the Executor interface. It is implemented as the ThreadPoolExecutor class, which is the core of the thread pool class. Therefore, to understand the thread pool in Java, you must first understand this class.

ThreadPoolExecutor inherits the AbstractExecutorService class and provides four constructors: AbstractExecutorService class AbstractExecutorService class

ThreadPoolExecutor inherits the AbstractExecutorService class and provides four constructors. In fact, if you look at the source code implementation of each constructor, you can see that the first three constructors are all initialized by the fourth constructor called.

ThreadPoolExecutor inherits the AbstractExecutorService class and provides four constructors. In fact, if you look at the source code implementation of each constructor, you can see that the first three constructors are all initialized by the fourth constructor called.

Here’s what the constructor parameters mean:

1. CorePoolSize (base size of thread pool)

When a task is submitted to the thread pool, the thread pool creates a thread to execute the task, even if other free base threads are able to execute new tasks, until the number of tasks that need to be executed exceeds the base size of the thread pool. If the thread pool’s prestartAllCoreThreads method is called, the pool creates and starts all base threads ahead of time.

2. RunnableTaskQueue

A blocking queue used to hold tasks waiting to be executed. You can choose from the following blocking queues.

  • ArrayBlockingQueue: ArrayBlockingQueue is a bounded blocking queue based on an array structure that sorts elements in FIFO (first-in, first-out) order.
  • LinkedBlockingQueue: A blocking queue based on a linked list structure that sorts elements in FIFO (first in, first out) and typically has a higher throughput than ArrayBlockingQueue.
  • SynchronousQueue: A blocking queue that does not store elements. Each insert operation must wait until another thread calls the remove operation, otherwise the insert operation remains blocked and throughput is usually higher than LinkedBlockingQueue.
  • PriorityBlockingQueue: An infinite blocking queue with a priority.
3. MaximumPoolSize (maximum thread pool size)

Maximum number of threads allowed to be created in a thread pool. If the queue is full and the number of threads created is less than the maximum, the thread pool creates a new thread to execute the task. Note that this parameter has no effect if the unbounded task queue is used.

ThreadFactory: Used to set the factory for creating threads

You can use thread factories to give each thread created a more meaningful name, and Debug and locate problems are very helpful.

5. RejectedExecutionHandler (saturated strategy)

When both the queue and the thread pool are full, the thread pool is saturated, and a policy must be adopted to handle submitted new tasks. This policy is AbortPolicy by default, indicating that an exception is thrown when a new task cannot be processed. Here are the four strategies that JDK1.5 provides. AbortPolicy: Directly throws an exception.

  • CallerRunsPolicy: Run the task only in the caller’s thread.
  • DiscardOldestPolicy: Discards the most recent task in the queue and executes the current task.
  • DiscardPolicy: Do not process, discard.
  • You can also customize the RejectedExecutionHandler interface based on application scenarios. Such as logging or persisting tasks that cannot be processed.
6. KeepAliveTime (thread activity hold time)

The amount of time that a worker thread in a thread pool remains alive after it is idle. Therefore, if there are many tasks and the execution time of each task is short, you can increase the duration to improve thread utilization.

7.TimeUnit

The options are in DAYS, HOURS, MINUTES, MILLISECONDS, MICROSECONDS and NANOSECONDS.

Considerations for thread pools

While thread pools can greatly improve the concurrency performance of a server, there are risks associated with using them. As with all multithreaded applications, applications built with thread pools are prone to various concurrency problems, such as competing for shared resources and deadlocks. In addition, thread pool-related issues such as deadlocks, insufficient system resources, and thread leaks can easily result if the thread pool itself is not implemented in a robust way, or if thread pools are not used properly.

1) Recommend using new ThreadPoolExecutor(…) To create a thread pool

The thread pool should be created by ThreadPoolExecutor instead of Executors. By doing so, you can know the parameters and operating rules of the thread pool and avoid resource depletion, as specified in the JAVA development manual of Alibaba. This should not be underestimated. There was a student who failed to create thread pools for multiple applications deployed on the same machine due to improper use of thread pools.

2) Set the number of threads reasonably

Set the number of working threads in the thread pool based on actual conditions. Do not set the number of working threads for CPU-intensive services such as searching and sorting, for which the CPU has little idle time.

If the task is CPU intensive, you need to squeeze as much CPU as possible. The reference value can be SET to NCPU+1

For IO intensive tasks, you can set the reference value to 2 x NCPU

3) Set the thread name that can represent the specific business

In this way, the log can be identified by the thread name. Concrete implementation can be specified by the ThreadFactory ThreadPoolExecutor parameters, such as making Spring CustomizableThreadFactory offers.