1. Introduction
The rational use of the thread pool can bring three benefits. First: Reduce resource consumption. Reduce the consumption caused by thread creation and destruction by reusing the threads that have been created. Second: Improve response speed. When a task arrives, the task can be executed without the need to wait for the thread to be created. Third: Improve the manageability of threads. Threads are scarce resources that, if created indefinitely, not only consume system resources, but also reduce system stability, using a thread pool for uniform allocation, tuning, and monitoring. But to make reasonable use of the thread pool, it must be well-versed in its principles.
2. Creating a thread pool using a thread pool
We can create a thread pool through threadpoolexecutor.
1 |
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, |
2 |
keepAliveTime, milliseconds,runnableTaskQueue, threadFactory,handler); |
To create a thread pool, you need to enter a few parameters:
- Corepoolsize (basic size of the thread pool): When a task is submitted to the thread pool, the line pool creates a thread to perform the task, and the thread is created even if other idle basic threads are able to perform new tasks, and is no longer created until the number of tasks that need to be executed is larger than the thread pool base size. If the thread pool's Prestartallcorethreads method is called, the thread pool creates and starts all basic threads in advance.
- Runnabletaskqueue (Task queue): A blocking queue that is used to hold tasks waiting to be executed. You can select the following blocking queues.
- Arrayblockingqueue: is a bounded blocking queue based on the array structure, which sorts the elements in FIFO (first-out) principle.
- Linkedblockingqueue: A blocking queue based on a linked list structure, which sorts elements in FIFO (first-out), with throughput typically higher than arrayblockingqueue. The static Factory Method Executors.newfixedthreadpool () uses this queue.
- Synchronousqueue: A blocking queue that does not store elements. Each insert operation must wait for another thread to invoke the remove operation, or the insert operation will always be in a blocked state, with throughput typically higher than linkedblockingqueue, and the static factory method Executors.newcachedthreadpool Use this queue.
- Priorityblockingqueue: An infinite blocking queue with priority.
- Maximumpoolsize (maximum thread pool size): The maximum number of threads allowed to be created by a thread pool. If the queue is full and the number of threads that have been created is less than the maximum number of threads, the thread pool will then create new threads to perform the task. It is worth noting that if you use the unbounded task queue This parameter has little effect.
- Threadfactory: Used to set the factory for creating threads, you can set a more meaningful name for each thread that is created through the thread factory, and it is very helpful to debug and locate problems.
Rejectedexecutionhandler (Saturation policy): When the queue and thread pool are full, indicating that the thread pools are saturated, a policy must be taken to handle the new tasks that are submitted. This policy is abortpolicy by default, indicating that an exception is thrown when a new task cannot be processed. The following are the four strategies provided by JDK1.5. N AbortPolicy: Throws an exception directly.
- Callerrunspolicy: Runs the task only with the caller's thread.
- Discardoldestpolicy: Discards the most recent task in the queue and executes the current task.
- Discardpolicy: not processed, discarded.
- It is also possible to implement the Rejectedexecutionhandler interface customization strategy According to the application scenario. Tasks such as logging or persistence that cannot be processed.
- KeepAliveTime (thread activity hold time): When the worker thread of the thread pool is idle, the time to remain alive. So if the task is a lot, and each task executes a short time, you can adjust the time to increase the utilization of the thread.
- Timeunit (unit of thread activity hold time): Optional units have day (days), hours (HOURS), minutes (MINUTES), milliseconds (MILLISECONDS), microseconds (microseconds, 1 per thousand milliseconds), and nanoseconds ( nanoseconds, 1 per thousand microseconds).
Submit a task to the thread pool
We can use the task submitted by execute, but the Execute method does not return a value, so it is not possible to tell whether the task is successfully executed by the thread pool. The following code shows that the task entered by the Execute method is an instance of the Runnable class.
01 |
threadsPool.execute( new Runnable() { |
06 |
// TODO Auto-generated method stub |
We can also use the Submit method to submit the task, it will return a future, then we can determine whether the task succeeds, through the future of the Get method to get the return value, the Get method will block until the task is completed, and use Get (long Timeout, Timeunit unit) method will be blocked for a period of time immediately after the return, it is possible that the task is not finished.
03 |
Object s = future.get(); |
05 |
} catch (InterruptedException e) { |
09 |
} catch (ExecutionException e) { |
Shutdown of the thread pool
We can turn the thread pool off by invoking the shutdown or Shutdownnow method of the thread pool, but they are implemented differently, and the shutdown principle is simply to set the thread pool's state to shutdown state and then break all threads that are not performing the task. The principle of shutdownnow is to traverse the worker threads in the thread pool and then call the thread's interrupt method one by one, so that a task that cannot respond to the interrupt may never be terminated. Shutdownnow first sets the status of the thread pool to stop, and then tries to stop all threads that are executing or pausing the task, and returns a list of waiting tasks to be performed.
The IsShutDown method returns true whenever one of the two closing methods is called. The call to the Isterminaed method returns True when all tasks have been closed to indicate that the thread pool was successfully closed. As to which method we should call to close the thread pool, it should be determined by the task attributes submitted to the thread pool, usually called shutdown to close the thread pool, and shutdownnow can be called if the task is not necessarily finished.
3. Thread pool Analysis
Process Analysis : The main workflow of the thread pool is as follows:
As we can see, when a new task is submitted to the thread pool, the thread pool processes the following:
- First, the thread pool determines if the base thread pool is full? Not full, create a worker thread to perform the task. Full, then go to the next process.
- Next the thread pool determines if the task queue is full? is not full, the newly submitted task is stored in the work queue. Full, then go to the next process.
- The last thread pool determines if the entire thread pool is full? is not full, a new worker thread is created to perform the task, and the saturation policy is assigned to handle the task.
Source code Analysis. The above process analysis allows us to intuitively understand how the thread pool works, so let's look at the source code to see how it is implemented. The thread pool performs the task in the following ways:
01 |
public void execute(Runnable command) { |
05 |
throw new NullPointerException(); |
07 |
//如果线程数小于基本线程数,则创建线程并执行当前任务 |
09 |
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) { |
11 |
//如线程数大于等于基本线程数或线程创建失败,则将当前任务放到工作队列中。 |
13 |
if (runState == RUNNING && workQueue.offer(command)) { |
15 |
if (runState != RUNNING || poolSize == 0 ) |
17 |
ensureQueuedTaskHandled(command); |
21 |
//如果线程池不处于运行中或任务无法放入队列,并且当前线程数量小于最大允许的线程数量,则创建一个线程执行任务。 |
23 |
else if (!addIfUnderMaximumPoolSize(command)) |
25 |
//抛出RejectedExecutionException异常 |
27 |
reject(command); // is shutdown or saturated |
worker Threads . When the thread pool creates threads, the thread is encapsulated as a worker thread worker,worker the tasks in the work queue are executed indefinitely after the task is completed. We can see this from the worker's Run method:
05 |
Runnable task = firstTask; |
09 |
while (task != null || (task = getTask()) != null ) { |
4. Reasonable configuration of the thread pool
To properly configure the thread pool, you must first analyze the task characteristics, which can be analyzed from the following angles:
- Nature of tasks: CPU-intensive tasks, IO-intensive tasks, and hybrid tasks.
- 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.
Tasks of a 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 configuring thread pooling for ncpu+1 threads. IO-intensive tasks are configured as many threads as possible, such as 2*NCPU, due to the need to wait for IO operations and the thread is not always performing the task. Mixed-type tasks, if they can be split into a CPU-intensive task and an IO-intensive task, as long as the time difference between the two tasks is not too large, then the throughput rate after decomposition is higher than the serial execution throughput rate, if the two task execution time is too large, it is not necessary to decompose. We can get the number of CPUs for the current device through the Runtime.getruntime (). Availableprocessors () method.
Tasks with different priority levels can be handled using the priority queue Priorityblockingqueue. It allows high-priority tasks to be executed first, and it is important to note that low-priority tasks may never be executed if a task with a high priority is committed to the queue.
Tasks with different execution times can be handled by different sizes of thread pools, or priority queues can be used to perform tasks that have a short execution time.
A task that relies on the database connection pool, because the thread submits the SQL and waits for the database to return the results, and the longer the CPU idle time waits, the greater the number of threads should be, so that the CPU can be better utilized.
It is recommended to use bounded queue , the bounded queue can increase the stability and early warning ability of the system, can be set to a larger point, such as thousands of. One time the queue and thread pool of the background task thread pool used by our group were full, and threw out the exception of the task, and by finding out that there was a problem with the database, the execution of SQL became very slow, because the tasks of the background task line constructor all needed to query and insert data into the database. So the work thread that causes the line constructor all blocks, the task backlog is constructor online. If we were to set up the unbounded queue, the thread pool would have more and more queues, potentially filling up the memory, making the whole system unusable, not just a background task. Of course all our system tasks are deployed with separate servers, and we use thread pools of different sizes to run different types of tasks, but this problem can also affect other tasks.
5. Monitoring of the thread pool
monitored by the parameters provided by the thread pool . Line constructor Some properties can be used when monitoring the thread pool
- Taskcount: The number of tasks that the thread pool needs to perform.
- Completedtaskcount: The number of tasks that the thread pool has completed during the run. Less than or equal to Taskcount.
- Largestpoolsize: The maximum number of threads that the thread pool has ever created. This data lets you know if the thread pool is full. Equal to the maximum size of the thread pool means that the thread pool was once full.
- Getpoolsize: The number of threads in the thread pool. If the thread pool is not destroyed, the threads in the pool will not be destroyed automatically, so this size only increases.
- Getactivecount: Gets the number of active threads.
Monitor by extending the thread pool . By inheriting the thread pool and overriding the Beforeexecute,afterexecute and terminated methods of the thread pool, we can do something before the task executes and before the thread pool shuts down. such as the average execution time of the monitoring task, the maximum execution time and the minimum execution time. These several methods of online constructor are empty methods. Such as:
1 |
<b>protected</b> <b>void</b> beforeExecute(Thread t, Runnable r) { } |
6. References
- Java concurrent programming in combat.
- JDK1.6 source code.
Analysis and use of the Java thread pool