In the last article, we introduced How to use the @ Async annotation to create asynchronous tasks , I can use this method to implement some concurrent operations to speed up the execution efficiency of tasks. However, if you just create it directly and simply as before, you may still encounter some problems. What are the problems? Let's first think about whether there are problems or risks in the implementation of the following interface through asynchronous task acceleration?
@RestController public class HelloController { @Autowired private AsyncTasks asyncTasks; @GetMapping("/hello") public String hello() { // Split the parallel processing logic into three asynchronous tasks to execute at the same time CompletableFuture<String> task1 = asyncTasks.doTaskOne(); CompletableFuture<String> task2 = asyncTasks.doTaskTwo(); CompletableFuture<String> task3 = asyncTasks.doTaskThree(); CompletableFuture.allOf(task1, task2, task3).join(); return "Hello World"; } }
Although, from a single interface call, there is no problem. However, when the interface is frequently called by the client, the number of asynchronous tasks will increase greatly: 3 x n (n is the number of requests). If the task processing is not fast enough, memory overflow is likely to occur. So why is there a memory overflow? The root cause is that the default thread pool used by Spring Boot for asynchronous tasks is configured as follows:
The two important parameters I marked in the figure need attention:
- queueCapacity: the capacity of the buffer queue. The default is the maximum value of INT (the 31st power of 2 - 1).
- maxSize: the maximum number of threads allowed. The default is the maximum value of INT (the 31st power of 2 - 1).
Therefore, by default, the general task queue may be full of memory. Therefore, when we really use it, we also need to make some basic configuration for the execution thread pool of asynchronous tasks to prevent the unavailability of services caused by memory overflow.
Configure default thread pool
The configuration of the default thread pool is very simple. You only need to complete it in the configuration file. The main parameters are as follows:
spring.task.execution.pool.core-size=2 spring.task.execution.pool.max-size=5 spring.task.execution.pool.queue-capacity=10 spring.task.execution.pool.keep-alive=60s spring.task.execution.pool.allow-core-thread-timeout=true spring.task.execution.shutdown.await-termination=false spring.task.execution.shutdown.await-termination-period= spring.task.execution.thread-name-prefix=task-
The specific configuration meaning is as follows:
- spring.task.execution.pool.core-size: the number of initialization threads when creating a thread pool. The default is 8
- spring.task.execution.pool.max-size: the maximum number of threads in the thread pool. The default value is int maximum
- spring.task.execution.pool.queue-capacity: used to buffer the queue for executing tasks. The default value is int maximum
- spring.task.execution.pool.keep-alive: the time allowed to remain idle before the thread terminates
- spring.task.execution.pool.allow-core-thread-timeout: whether the core thread is allowed to timeout
- spring.task.execution.shutdown.await-termination: whether to close the application after the remaining tasks are completed
- spring.task.execution.shutdown.await-termination-period: the maximum time to wait for the remaining tasks to complete
- spring.task.execution.thread-name-prefix: the prefix of the thread name. After setting, it is convenient for us to view the thread pool where the processing task is located in the log
Try it yourself
We directly perform the following operations based on the results of the previous chapter 7-5.
First, you can perform the following unit tests before configuring the thread pool:
@Test public void test1() throws Exception { long start = System.currentTimeMillis(); CompletableFuture<String> task1 = asyncTasks.doTaskOne(); CompletableFuture<String> task2 = asyncTasks.doTaskTwo(); CompletableFuture<String> task3 = asyncTasks.doTaskThree(); CompletableFuture.allOf(task1, task2, task3).join(); long end = System.currentTimeMillis(); log.info("Complete all tasks, total time:" + (end - start) + "millisecond"); }
Since the number of core threads in the default thread pool is 8, three tasks will be executed at the same time. The log output is as follows:
2021-09-15 00:30:14.819 INFO 77614 --- [ task-2] com.didispace.chapter76.AsyncTasks : Start task two 2021-09-15 00:30:14.819 INFO 77614 --- [ task-3] com.didispace.chapter76.AsyncTasks : Start task three 2021-09-15 00:30:14.819 INFO 77614 --- [ task-1] com.didispace.chapter76.AsyncTasks : Start task one 2021-09-15 00:30:15.491 INFO 77614 --- [ task-2] com.didispace.chapter76.AsyncTasks : Completion of task 2 takes 672 milliseconds 2021-09-15 00:30:19.496 INFO 77614 --- [ task-3] com.didispace.chapter76.AsyncTasks : Completing task 3 takes 4677 milliseconds 2021-09-15 00:30:20.443 INFO 77614 --- [ task-1] com.didispace.chapter76.AsyncTasks : Completion of task 1 takes 5624 milliseconds 2021-09-15 00:30:20.443 INFO 77614 --- [ main] c.d.chapter76.Chapter76ApplicationTests : Complete all tasks, total time: 5653 milliseconds
Next, you can try to add the following thread pool configuration to the configuration file
spring.task.execution.pool.core-size=2 spring.task.execution.pool.max-size=5 spring.task.execution.pool.queue-capacity=10 spring.task.execution.pool.keep-alive=60s spring.task.execution.pool.allow-core-thread-timeout=true spring.task.execution.thread-name-prefix=task-
The order of log output will change to the following order:
2021-09-15 00:31:50.013 INFO 77985 --- [ task-1] com.didispace.chapter76.AsyncTasks : Start task one 2021-09-15 00:31:50.013 INFO 77985 --- [ task-2] com.didispace.chapter76.AsyncTasks : Start task two 2021-09-15 00:31:52.452 INFO 77985 --- [ task-1] com.didispace.chapter76.AsyncTasks : Completing task 1 takes 2439 milliseconds 2021-09-15 00:31:52.452 INFO 77985 --- [ task-1] com.didispace.chapter76.AsyncTasks : Start task three 2021-09-15 00:31:55.880 INFO 77985 --- [ task-2] com.didispace.chapter76.AsyncTasks : Completing task 2 takes 5867 milliseconds 2021-09-15 00:32:00.346 INFO 77985 --- [ task-1] com.didispace.chapter76.AsyncTasks : Completion of task 3 takes 7894 milliseconds 2021-09-15 00:32:00.347 INFO 77985 --- [ main] c.d.chapter76.Chapter76ApplicationTests : All tasks completed, total time: 10363 milliseconds
- Task 1 and task 2 will immediately occupy the core thread, and task 3 will enter the queue and wait
- Once the task is completed, a core thread is released. Task 3 is removed from the queue and occupies the core thread to start processing
Note: some partners may ask why the maximum thread is not 5. Why does task 3 enter the buffer queue instead of creating a new thread for processing? Here, we need to understand the relationship between the buffer queue and the maximum thread: only when the buffer queue is full will we apply for threads exceeding the number of core threads for processing. Therefore, only when 10 tasks in the buffer queue are full and the 11th task comes, will a third thread be created in the thread pool for processing. There is no specific list here. Readers can adjust the parameters or adjust the unit test to verify the logic.
This series of tutorials "Spring Boot 2.x basic tutorial" click directly! , welcome to collect and forward! If you encounter difficulties in the learning process? You can join us Spring technology exchange group , participate in communication and discussion, better learning and progress!
Code example
For the complete project of this article, you can view the chapter7-6 project under the 2.x directory in the following warehouse:
- Github: https://github.com/dyc87112/SpringBoot-Learning/
- Gitee: https://gitee.com/didispace/SpringBoot-Learning/
If you think this article is good, welcome Star's support. Your attention is the driving force of my persistence!
Welcome to my official account: program ape DD, sharing knowledge and thinking that I can't see elsewhere.