When using @ async in spring boot, don’t forget the configuration of thread pool!

Time:2021-11-25

In the last article, we introducedHow to use@AsyncAnnotation 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 task1 = asyncTasks.doTaskOne();
        CompletableFuture task2 = asyncTasks.doTaskTwo();
        CompletableFuture 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 max
  • spring.task.execution.pool.keep-alive: the time allowed to remain idle before a thread terminates
  • spring.task.execution.pool.allow-core-thread-timeout: whether core threads are allowed to time out
  • spring.task.execution.shutdown.await-termination: do you want to wait for the remaining tasks to complete before closing the app
  • spring.task.execution.shutdown.await-termination-period: the maximum time to wait for the remaining tasks to complete
  • spring.task.execution.thread-name-prefix: prefix of 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 are directly based on the previouschapter7-5The following operations are carried out according to the results of.

First, you can perform the following unit tests before configuring the thread pool:

@Test
public void test1() throws Exception {
    long start = System.currentTimeMillis();

    CompletableFuture task1 = asyncTasks.doTaskOne();
    CompletableFuture task2 = asyncTasks.doTaskTwo();
    CompletableFuture task3 = asyncTasks.doTaskThree();

    CompletableFuture.allOf(task1, task2, task3).join();

    long end = System.currentTimeMillis();

    Log.info ("all tasks completed, total time spent:" + (end - start) + "milliseconds");
}

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 2
2021-09-15 00:30:14.819 info 77614 -- [TASK-3] com.didispace.chapter76.asynctasks: start task 3
2021-09-15 00:30:14.819 info 77614 -- [TASK-1] com.didispace.chapter76.asynctasks: start task 1
2021-09-15 00:30:15.491 info 77614 -- [TASK-2] com.didispace.chapter76.asynctasks: completing 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: completing task 1 takes 5624 milliseconds
2021-09-15 00:30:20.443 info 77614 -- [main] c.d.chapter76.chapter76applicationtests: all tasks are completed, 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 1
2021-09-15 00:31:50.013 info 77985 -- [TASK-2] com.didispace.chapter76.asynctasks: start task 2
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 3
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: completing task 3 takes 7894 milliseconds
2021-09-15 00:32:00.347 info 77985 -- [main] c.d.chapter76.chapter76applicationtests: all tasks are 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 usSpring technology exchange group, participate in communication and discussion, better learning and progress!

Code example

The complete project of this article can be viewed in the following warehouse2.xUnder directorychapter7-6Project:

If you think this article is good, welcomeStarSupport, your attention is the driving force of my persistence!

Welcome to my official account: program ape DD, sharing dry cargo and thinking outside!

Recommended Today

Apache sqoop

Source: dark horse big data 1.png From the standpoint of Apache, data flow can be divided into data import and export: Import: data import. RDBMS—–>Hadoop Export: data export. Hadoop—->RDBMS 1.2 sqoop installation The prerequisite for installing sqoop is that you already have a Java and Hadoop environment. Latest stable version: 1.4.6 Download the sqoop installation […]