When using @ Async in Spring Boot, don't forget the configuration of thread pool!

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:

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.

Posted by mikewooten on Sat, 18 Sep 2021 23:25:21 -0700