In the previous article, we introduced how uses the @Async
annotation to create an asynchronous task . I can use this method to implement some concurrent operations to speed up the execution efficiency of the task. However, if you just create and use it directly as above, you may still encounter some problems. What's the problem? Let's think about it first. Is there any problem or risk in the implementation of the following interface through asynchronous tasks to accelerate the execution?
@RestController
public class HelloController {
@Autowired
private AsyncTasks asyncTasks;
@GetMapping("/hello")
public String hello() {
// 将可以并行的处理逻辑,拆分成三个异步任务同时执行
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. But when the interface is frequently called by the client, the number of asynchronous tasks will increase greatly: 3 xn (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 Spring Boot's default thread pool for asynchronous tasks is configured like this:
The two important parameters I have marked in the figure need to be paid attention to:
queueCapacity
: the capacity of the buffer queue, the default is the maximum value of INT (2 to the 31st power-1).maxSize
: The maximum number of threads allowed, the default is the maximum value of INT (2 to the 31st power -1).
Therefore, by default, the general task queue may be full of memory. Therefore, when we actually use it, we need to do some basic configuration of the execution thread pool of asynchronous tasks to prevent the problem of unavailability of services due to memory overflow.
Configure the default thread pool
The configuration of the default thread pool is very simple. It only needs to be completed 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 initial threads when the thread pool is created, the default is 8spring.task.execution.pool.max-size
: the maximum number of threads in the thread pool, the default is the maximum value of intspring.task.execution.pool.queue-capacity
: the queue used to buffer the execution of the task, the default is the maximum value of intspring.task.execution.pool.keep-alive
: The time allowed to remain idle before the thread terminatesspring.task.execution.pool.allow-core-thread-timeout
: Whether to allow the core thread to time outspring.task.execution.shutdown.await-termination
: Whether to wait for the remaining tasks to complete before closing the applicationspring.task.execution.shutdown.await-termination-period
: the maximum time to wait for the completion of the remaining tasksspring.task.execution.thread-name-prefix
: The prefix of the thread name, after setting it, it is convenient for us to view the thread pool where the processing task is located in the log
Give it a try
We directly perform the following operations chapter7-5
First of all, before configuring the thread pool, you can perform the following unit tests:
@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("任务全部完成,总耗时:" + (end - start) + "毫秒");
}
Since the number of core threads of the default thread pool is 8, so 3 tasks will start to execute at the same time, the log output is like this:
2021-09-15 00:30:14.819 INFO 77614 --- [ task-2] com.didispace.chapter76.AsyncTasks : 开始做任务二
2021-09-15 00:30:14.819 INFO 77614 --- [ task-3] com.didispace.chapter76.AsyncTasks : 开始做任务三
2021-09-15 00:30:14.819 INFO 77614 --- [ task-1] com.didispace.chapter76.AsyncTasks : 开始做任务一
2021-09-15 00:30:15.491 INFO 77614 --- [ task-2] com.didispace.chapter76.AsyncTasks : 完成任务二,耗时:672毫秒
2021-09-15 00:30:19.496 INFO 77614 --- [ task-3] com.didispace.chapter76.AsyncTasks : 完成任务三,耗时:4677毫秒
2021-09-15 00:30:20.443 INFO 77614 --- [ task-1] com.didispace.chapter76.AsyncTasks : 完成任务一,耗时:5624毫秒
2021-09-15 00:30:20.443 INFO 77614 --- [ main] c.d.chapter76.Chapter76ApplicationTests : 任务全部完成,总耗时:5653毫秒
Then, you can try to add the following thread pool configuration in 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 become as follows:
2021-09-15 00:31:50.013 INFO 77985 --- [ task-1] com.didispace.chapter76.AsyncTasks : 开始做任务一
2021-09-15 00:31:50.013 INFO 77985 --- [ task-2] com.didispace.chapter76.AsyncTasks : 开始做任务二
2021-09-15 00:31:52.452 INFO 77985 --- [ task-1] com.didispace.chapter76.AsyncTasks : 完成任务一,耗时:2439毫秒
2021-09-15 00:31:52.452 INFO 77985 --- [ task-1] com.didispace.chapter76.AsyncTasks : 开始做任务三
2021-09-15 00:31:55.880 INFO 77985 --- [ task-2] com.didispace.chapter76.AsyncTasks : 完成任务二,耗时:5867毫秒
2021-09-15 00:32:00.346 INFO 77985 --- [ task-1] com.didispace.chapter76.AsyncTasks : 完成任务三,耗时:7894毫秒
2021-09-15 00:32:00.347 INFO 77985 --- [ main] c.d.chapter76.Chapter76ApplicationTests : 任务全部完成,总耗时:10363毫秒
- Task 1 and Task 2 will immediately occupy the core thread, and Task 3 enters the queue to wait
- Once the task is completed, a core thread is released, task three is removed from the queue, and the core thread is occupied to start processing
Note: Some friends here may ask, isn’t the maximum thread 5? Why does task three enter the buffer queue instead of creating a new thread to process it? Here we need to understand the relationship between the buffer queue and the largest thread: only after the buffer queue is full, will the threads that exceed the number of core threads be applied for processing. Therefore, only 10 tasks in the buffer queue are full here, and when the eleventh task comes, a third thread will be created in the thread pool for processing. I won’t write specific examples here. Readers can adjust the parameters or adjust the unit tests to verify this logic.
This series of tutorials "Spring Boot 2.x basic tutorial" click directly! , welcome to collect and forward! What if you encounter difficulties in the learning process? You can join our Spring technical exchange group , participate in exchanges and discussions, and learn and progress better!
Code example
The complete works of this paper can be viewed below warehouse 2.x
directory chapter7-6
project:
- Github:https://github.com/dyc87112/SpringBoot-Learning/
- Gitee:https://gitee.com/didispace/SpringBoot-Learning/
If you think this article is good, welcome Star
support, your attention is my motivation for persistence!
Welcome to pay attention to my public account: Program Ape DD, to share knowledge and thoughts that can not be seen elsewhere
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。