Introduction to Thread Pools
Using the thread pool can well improve performance, the thread pool at the beginning of the run will create a certain number of idle thread, we will be a task to the thread pool, the thread pool will use an idle thread to perform the task, the task after the execution, the thread will not die, but again into idle state to return to the thread pool, waiting for the arrival of the next task. When using the thread pool, we to submit to perform a task for the whole thread pool, rather than submit to a thread, the thread pool to submit task, will seek whether there is any idle thread internally, if any, will be submitted to this task a free thread, although a thread at the same time can only perform a task, But we can submit multiple tasks to a thread pool. There are several advantages to using thread pools wisely: ① Reduce resource consumption during multithreading, the system constantly start and close the new thread, the cost is high, will excessive consumption of system resources, through the reuse of existing threads, reduce the object creation, death overhead ② improve the response speed when there is a task arrived, the task can not wait for the creation of the thread, Can just take out the idle thread from the thread pool thread to perform the task (3) convenient management thread for computer is very scarce resources, if let him to create unlimited, it not only consumes system resources, also can reduce the stability of the system, we use the thread pool can be unified allocation and monitoring after referring to the thread pool will think of pooling technology, The idea is to put a valuable resource into a pool, take it out of the pool every time you use it, and then put it back into the pool for someone else to use. So how is thread pooling implemented in Java?
Java four thread pools
The Java Executors tool class provides four methods for creating thread pools in different scenarios, respectively:
newSingleThreadExecutor
Only one thread is used to execute tasks, which is suitable for sequential tasks. If the unique thread terminates due to an exception, a new thread replaces it, ensuring that the tasks are executed in the specified order (FIFO, LIFO), and specifying thread factories (ThreadFactory
), you can customize the thread creation behaviornewFixedThreadPool
A thread pool with a fixed number of threads, only core threads, which is the maximum number of threads, and no non-core threads. A thread is created each time a task is submitted until the maximum size of the thread pool is reached. Once the thread pool reaches its maximum value, it remains the same, and if one of the threads terminates due to an exception, a new thread is added to the pool. It can also control the maximum number of concurrent threads, which will be blocked in the queue (LinkedBlockingQueue
), which can also specify thread factories (ThreadFactory
), you can customize the thread creation behavior.newCachedThreadPool
Create a cacheable thread pool with a maximum number of threads2^ 31-1 (integer.max_value)
If no thread pool is available, a new thread will be created. If the size of the thread pool exceeds the number of threads needed to process the task, some idle threads (60s) will be recycled.newScheduledThreadPool
A pool of threads that periodically execute tasks on a specific schedule. There are core threads, but there are also non-core threads, which are also infinite in size (Integer.MAX_VALUE:2^31 - 1
) for performing periodic tasks.
Java thread pool parameters
The following uses the Thread pool variables provided by the Executors tool to implement thread pools in various scenarios: * If the thread pool is used by the Executors tool, the following uses the Thread pool variable to implement thread pools: * If the thread pool is used by the Executors tool, the thread pool variable is used by the Executors tool. The first is the CTL, which declares as follows:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
Copy the code
The member variable CTL is used to store the working state of the thread pool and the number of threads running in the pool. Obviously, to store two pieces of data in an integer variable, you can only split it in two. The high 3 bits are used to store the state of the thread, and the low 29 bits are used to store the number of threads executing in the thread pool.
Status of the thread pool
ThreadPoolExecutor defines five states of a thread pool (note that this is the state of the thread pool, not the state of the threads in the pool), which is RUNNING when a thread pool is created.
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
Copy the code
Thread pool state | meaning |
---|---|
RUNNING | Allow tasks to be submitted and processed |
SHUTDOWN | New submitted tasks are not processed, but processed tasks are completed |
STOP | New submitted tasks are not processed, unexecuted tasks in the blocking queue are not processed, and the interrupt flag bit for executing tasks is set |
TIDYING | The number of working threads in the thread pool is 0, waiting to execute the terminated() hook method |
TERMINATED | Terminated () hook method is complete |
Call the shutdown method of the thread pool to change the state from RUNNING to shutdown. Call the shutdownNow method to change the thread pool from the RUNNING state to the STOP state. Both the SHUTDOWN and STOP states change to TIDYING and TERMINATED. As shown in the figure:
ThreadPoolExecutor also provides the following three methods to view the state of the thread pool and the number of threads executing in the pool
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
Copy the code
Constructor of ThreadPoolExecutor
The most complete constructor for this class determines the various attributes of the created thread pool:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
Copy the code
Meanings of each parameter: CorePoolSize Maximum number of core threads in the thread pool maximumPoolSize Maximum number of threads in the thread pool keepAliveTime Lifetime of idle threads Unit Unit of lifetime of idle threads workQueue Used to store the blocking queue of tasks ThreadFactory creates a threadFactory handler the policy used by the thread pool to continue adding new tasks when the workQueue is full and the number of threads in the pool reaches maximumPoolSize
Here is a diagram to better understand the thread pool parameters:
The corePoolSize, maximumPoolSize, and workQueue relationships are shown by adding new tasks to the thread pool:
- If there are no free threads to perform the task and the number of threads running in the pool is less than
corePoolSize
, a new thread is created to perform the task - If there are no free threads to perform the task, and if the number of threads executing in the pool is greater than
corePoolSize
“, the newly added task entersworkQueue
Queue up (ifworkQueue
Length allowed), waiting for an idle thread to execute - If there are no free threads to perform the task, and the blocking queue is full and the number of threads in the pool is less than
maximumPoolSize
, a new thread is created to perform the task - If there are no free threads to perform the task and the blocking queue is full the number of threads in the pool equals
maximumPoolSize
, according to the constructorhandler
Specifies a policy to reject newly added tasks
The thread pool does not mark which threads are core and which are not core. The thread pool only cares about the number of core threads. The following is an image of a metaphor seen on the Internet:
If a thread pool is a unit, corePoolSize represents a regular worker and a thread represents an employee. When we assign a job to a company, if the company finds that a regular worker is not available, the company will hire a regular worker to complete the job. As we delegate more and more work to the organization, even if all the regular workers are full, the organization will have to find a place to put the newly assigned work in order. This place is called the workQueue, and when the regular workers have finished their work, they will come here to pick up new tasks. If it happens to be the end of the year and there’s no room in the workQueue for new tasks, the organization decides to hire temporary workers (temporary worker: me again!). . MaximumPoolSize maximumPoolSize maximumPoolSize corePoolSize maximumPoolSize maximumPoolSize corePoolSize maximumPoolSize corePoolSize maximumPoolSize corePoolSize maximumPoolSize corePoolSize maximumPoolSize corePoolSize maximumPoolSize corePoolSize maximumPoolSize corePoolSize maximumPoolSize corePoolSize maximumPoolSize corePoolSize Of course, in a thread pool, there is no difference between who is a regular worker and who is a temporary worker, and it is completely equal pay for equal work.
KeepAliveTime and unit
KeepAliveTime indicates that threads that exceed the number of corePoolSize are cleared after their idle time is greater than keepAliveTime.
WorkQueue Task queue
WorkQueue determines the queue strategy for cache tasks. Different policies can be adopted for different task scenarios. This queue requires a task queue that implements the BlockingQueue interface. According to the Documentation for ThreadPoolExecutor, three queues are officially recommended: SynchronousQueue, LinkedBlockingQueue, and ArrayBlockingQueue. SynchronousQueue and ArrayBlockingQueue are finite queues, while LinkedBlockingQueue is infinite.
SynchronousQueue
: a blocking queue that does not store elements. Each insert operation must wait for another thread to call the remove operation, otherwise the insert operation is always blocked and throughput is usually higherLinkedBlockingQueue
Static factory methodExecutors.newCachedThreadPool
This queue is used.ArrayBlockingQueue
: bounded blocking queue. A bounded blocking queue supported by arrays. This queue isFIFO
The (first-in, first-out) principle sorts elements. The new element is inserted at the end of the queue, and the queue fetch operation retrieves the element from the head of the queue. This is a typical “bounded cache,” where arrays of fixed size hold elements inserted by producers and extracted by consumers. Once such a cache is created, its capacity cannot be increased. Trying to put an element into a full queue blocks the operation, and trying to extract an element from an empty queue causes a similar block.LinkedBlockingQueue
: a blocking queue of linked list structures, with elements inserted at the tail and removed at the head.LinkedBlockingQueue
Are we inThreadPoolExecutor
A common wait queue in a thread pool. It may or may not specify a capacity. Due to its “infinite capacity” nature, virtually any queue/stack of infinite capacity has capacity, and this capacity isInteger.MAX_VALUE
.LinkedBlockingQueue
The implementation is based on a linked list structure rather than similarArrayBlockingQueue
An array like that. But in practice, you don’t need to care about its internal implementation, if specifiedLinkedBlockingQueue
The capacity size, then it reflects the use of characteristics andArrayBlockingQueue
Similar.
ThreadFactory Factory for creating threads
The default factory is the threadFactory used in constructors like ThreadPoolExecutor that have no threadFactory argument, such as the following constructor:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
Copy the code
In this constructor, create a thread factory USES Executors. DefaultThreadFactory () in the factories and ThreadPoolExecutor defaultHandler abandoning strategy by default. Use the Executors. DefaultThreadFactory created Thread synchronization, belong to the same Thread group has as Thread. NORM_PRIORITY priority, And called the pool – poolNumber. GetAndIncrement () – thread – the thread name (poolNumber. GetAndIncrement () thread pool order number), and create threads are daemon.
Handler rejection policy
Represents the policy adopted by the thread pool to refuse to add new tasks when the workQueue is full and the number of threads in the pool reaches maximumPoolSize. As you can see from the documentation, the handler can generally take four values:
Rejection policies | meaning |
---|---|
AbortPolicy | Throw RejectedExecutionException abnormal |
CallerRunsPolicy | The task is executed by the thread that submitted the task to the thread pool |
DiscardPolicy | The current task is discarded |
DiscardOldestPolicy | Discard the oldest task (the one submitted first and not executed) |
Personally, the most elegant approach is the one AbortPolicy provides: throw an exception and let the developer handle it. ThreadPoolExecutor way defaultHandler ThreadPoolExecutor is by default. AbortPolicy.
Configure thread pools properly
Finally, to properly configure the thread pool, we must first analyze the task characteristics, which can be analyzed from the following perspectives:
Nature of the task
Tasks of different nature can be handled separately by thread pools of different sizes. CPU intensive tasks are configured with as few threads as possible, such as Ncpu+1 thread pool. For IO-intensive tasks, threads do not execute tasks all the time because they need to wait for I/O operations. Therefore, configure as many threads as possible, such as 2 x Ncpu. Hybrid task, if you can break up, it is split into a cpu-intensive task and an IO intensive tasks, as long as the two task execution time difference is not too big, then decomposed execution throughput than the throughput of serial execution, if the huge difference between the two task execution time, don’t need to split up. We can use runtime.getruntime ().availableProcessors() to get the number of cpus on the current device.
Task priority
Tasks with different priorities can be processed using the PriorityBlockingQueue. It allows the higher-priority tasks to be executed first. Note that if higher-priority tasks are always submitted to the queue, the lower-priority tasks may never be executed.
Execution time of the task
Tasks with different execution times can be assigned to thread pools of different sizes, or priority queues can be used to allow shorter tasks to be executed first.
Task dependency
A task that depends on the database connection pool. If a thread submits SQL and waits for the database to return the result, the longer the wait, the longer the CPU idle time, the larger the number of threads should be set to better use the CPU. It is recommended to use the bounded queue. The bounded queue can increase the stability and early warning ability of the system. It can be set as larger as required, such as thousands. Once, the queue and thread pool of the background task thread pool used by our group were all full, and we constantly threw exceptions to abandon tasks. Through investigation, we found that there was a problem in the database, which led to the slow execution of SQL, because all tasks in the background task thread pool needed to query and insert data into the database. As a result, all worker threads in the thread pool are blocked, and tasks are backlogged in the thread pool. If we had set the queue to unbounded, the thread pool would have become so large that it would have overwhelmed memory and rendered the entire system unusable, not just the background tasks. Of course all the tasks on our system are deployed on a separate server, and we use thread pools of different sizes to run different types of tasks, but problems like this can affect other tasks as well.
See article: Analysis of JAVA thread pools and workQueue task queues using ThreadPoolExecutor