Spring comes with a thread pool is very convenient to use, but in relatively complex concurrent programming scenarios, the use of the scenario still requires careful consideration of the configuration, or you may encounter the pitfalls mentioned in this article.
Specific code reference sample project
ThredPoolTaskExcutor has 2 core configurations, one is the thread pool size and one is the queue size. The processing flow of
ThredPoolTaskExcutor: New threads are created and requests are processed until the thread count size is equal to corePoolSize; requests are put into workQueue and free threads in the thread pool go to workQueue to fetch tasks and process them; when workQueue is full, new threads are created and requests are processed and when the thread pool size is equal to maximumPoolSize,
RejectedExecutionHandler will be used to do the rejection process.
There are four types of Reject policies.
AbortPolicypolicy, which is the default policy, rejects the request and throws an exception RejectedExecutionException.
CallerRunsPolicypolicy , the task is executed by the calling thread.
DiscardPolicypolicy that rejects the request without throwing an exception.
DiscardOldestPolicypolicy to discard the first task to enter the queue.
2. Exceptions where multiple asynchronous processes share the same thread pool
Simulates a time-consuming operation that is set to execute asynchronously by the
@Async annotation. async will use a thread pool named taskExecutor by default. The operation returns a
CompletableFuture, and subsequent processing waits for the asynchronous operation to complete.
Set the thread pool size to 2 and the queue to a value larger than the thread pool, in this case 10. When the queue size is larger than or equal to the thread pool size, the program will block as in this article.
3. Problem Analysis
After the program starts, it blocks soon. Checking the thread status through jstack, we find that taskExecutor-1, taskExecutor-2 and main are in WAITING state, waiting for the execution of
CompletableFuture.join method to finish.
By analyzing the execution process of the program, it is not difficult to find the reason of blocking. As the size of the Queue set by the thread pool is larger than the size of the thread pool, when the thread pool is full, the delayFoo method will be in the queue, and as the program is executed, there will always be a situation where the thread pool is full of
CompletableFuture.join methods and the queue is full of delayFoo methods.
At this time, the join method in the thread is waiting for the execution of the delayFoo method in the queue to complete, and the delayFoo method in the queue cannot be executed because it cannot wait for the available threads, and the whole program is in a deadlock state.
The solution is simple: set the size of the queue to a size smaller than the number of threads, so that the methods in the queue will have a chance to get threads, and thus will not enter a deadlock state because the threads are full.