In the last article we showed 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 of tasks. However, if you simply create and use it as described above, you may still run into some problems. What’s the problem with existence? Is there a problem or risk with the implementation of asynchronous task acceleration?

@RestController
public class HelloController {

    @Autowired
    private AsyncTasks asyncTasks;
        
    @GetMapping("/hello")
    public String hello(a) {
        // Divide the parallel processing logic into three asynchronous tasks to execute simultaneously
        CompletableFuture<String> task1 = asyncTasks.doTaskOne();
        CompletableFuture<String> task2 = asyncTasks.doTaskTwo();
        CompletableFuture<String> task3 = asyncTasks.doTaskThree();
        
        CompletableFuture.allOf(task1, task2, task3).join();
        return "Hello World"; }}Copy the code

Although, in terms of a single interface call, there is no problem. However, when the interface is frequently invoked by the client, the number of asynchronous tasks increases dramatically: 3 x n (n is the number of requests), and if the task is not processed fast enough, it is likely to run out of memory. So why do you run out of memory? The root cause is that Spring Boot’s default thread pool for asynchronous tasks is configured like this:

There are two important parameters that I’ve highlighted here that need to be looked at:

  • queueCapacity: The capacity of the buffer queue, which defaults to the maximum value of INT (2 ^ 31 -1).
  • maxSize: Maximum number of threads allowed. Default is the maximum value of INT (2 ^ 31 -1).

So, by default, a typical task queue can fill up memory. So, when we actually use it, we also need to do some basic configuration on the execution thread pool of asynchronous tasks to prevent overflow of memory and make the service unavailable.

Configure the default thread pool

The default thread pool configuration is very simple and 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-
Copy the code

The configuration meanings are as follows:

  • spring.task.execution.pool.core-size: Number of initialized threads when the thread pool is created. The default is 8
  • spring.task.execution.pool.max-size: Specifies the maximum number of threads in the thread pool. The default value is int
  • spring.task.execution.pool.queue-capacity: The queue used to buffer the execution of the task. The default value is int Max
  • spring.task.execution.pool.keep-alive: The amount of time a thread is allowed to remain idle before terminating
  • spring.task.execution.pool.allow-core-thread-timeout: Whether to allow the core thread to timeout
  • spring.task.execution.shutdown.await-termination: Indicates whether to close the application after remaining tasks are completed
  • spring.task.execution.shutdown.await-termination-period: Indicates the maximum time to wait for remaining tasks to complete
  • spring.task.execution.thread-name-prefix: the prefix of the thread name, which allows us to view the thread pool in the log for processing tasks

Give it a try

Let’s do the following directly based on the previous chapter7-5 results.

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

@Test
public void test1(a) 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("All tasks completed, total time:" + (end - start) + "毫秒");
}
Copy the code

Since the default thread pool has a core thread count of 8, three tasks are started at the same time, and the log output looks like this:

The 2021-09-15 00:30:14. 77614-819 the INFO [task - 2] com. Didispace. Chapter76. AsyncTasks: Start task two 00:30:14 2021-09-15. 77614-819 the INFO/task - 3 com. Didispace. Chapter76. AsyncTasks: Start doing task three 00:30:14 2021-09-15. 819 INFO - 77614 [task - 1] com. Didispace. Chapter76. AsyncTasks: Began to do the task a 00:30:15 2021-09-15. 77614-491 the INFO/task - 2 com. Didispace. Chapter76. AsyncTasks: To complete the task two, time-consuming: 672 milliseconds 00:30:19 2021-09-15. 77614-496 the INFO/task - 3 com. Didispace. Chapter76. AsyncTasks: To complete the task 3, time-consuming: 4677 milliseconds 00:30:20 2021-09-15. 77614-443 the INFO] [task - 1 com. Didispace. Chapter76. AsyncTasks: To complete the task, a time-consuming: 5624 milliseconds 00:30:20 2021-09-15. 77614-443 the INFO [main] C.D.C. hapter76. Chapter76ApplicationTests: The task takes 5653 milliseconds to completeCopy the code

Next, try adding 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-
Copy the code

The log output sequence is as follows:

The 2021-09-15 00:31:50. 77985-013 the INFO [task - 1] com. Didispace. Chapter76. AsyncTasks: Began to do the task a 00:31:50 2021-09-15. 77985-013 the INFO/task - 2 com. Didispace. Chapter76. AsyncTasks: Start task two 00:31:52 2021-09-15. 77985-452 the INFO] [task - 1 com. Didispace. Chapter76. AsyncTasks: To complete the task, a time-consuming: 2439 milliseconds 00:31:52 2021-09-15. 77985-452 the INFO] [task - 1 com. Didispace. Chapter76. AsyncTasks: Start doing task three 00:31:55 2021-09-15. 880 INFO - 77985 [task - 2] com. Didispace. Chapter76. AsyncTasks: To complete the task two, time-consuming: 5867 milliseconds 00:32:00 2021-09-15. 77985-346 the INFO] [task - 1 com. Didispace. Chapter76. AsyncTasks: To complete the task 3, time-consuming: 7894 milliseconds 00:32:00 2021-09-15. 77985-347 the INFO [main] C.D.C. hapter76. Chapter76ApplicationTests: The task was complete, and the total time was 10363 msCopy the code
  • Task 1 and task 2 immediately occupy the core thread, and task 3 enters the queue and waits
  • Task one completes, releasing a core thread, task three moves out of the queue and occupies the core thread to start processing

Note: if the maximum number of threads is 5, why is task 3 queued in the buffer instead of creating a new thread? Understand the relationship between the buffered queue and the maximum number of threads: only after the buffered queue is full will more threads be requested for processing. So, a third thread is created in the thread pool only when the buffer queue is full of 10 tasks and the 11th task comes in. I won’t write an example of this here, but readers can adjust the parameters themselves, or adjust the unit tests to verify the logic.

Spring Boot 2.x Basic Tutorial , welcome to collect and forward! If you encounter difficulties in learning? You can join our Spring technology exchange group, participate in exchange and discussion, better learning and progress!

Code sample

For the complete project of this article, see the chapter7-6 project in the 2.x directory of the warehouse below:

  • Github:github.com/dyc87112/Sp…
  • Gitee:gitee.com/didispace/S…

If you found this article good, welcomeStarSupport, your attention is my motivation!

Welcome to pay attention to my public account: program ape DD, share the outside can not see the dry goods and thinking!