From the previous article on configuring @Async thread pools for asynchronous tasks, you should have seen that there is a thread pool behind the execution of asynchronous tasks to manage the execution of tasks. In order to control the concurrency of asynchronous tasks without affecting the normal operation of the application, we must configure the thread pool accordingly to prevent the excessive use of resources. In addition to the default thread pool configuration, another scenario that is common is thread pool isolation in the case of multitasking.

What is thread pool isolation and why

If you don’t know what thread pool isolation is, why? . So, let’s take a look at the following scenario example:

@RestController
public class HelloController {

    @Autowired
    private AsyncTasks asyncTasks;
        
    @GetMapping("/api-1")
    public String taskOne(a) {
        CompletableFuture<String> task1 = asyncTasks.doTaskOne("1");
        CompletableFuture<String> task2 = asyncTasks.doTaskOne("2");
        CompletableFuture<String> task3 = asyncTasks.doTaskOne("3");
        
        CompletableFuture.allOf(task1, task2, task3).join();
        return "";
    }
    
    @GetMapping("/api-2")
    public String taskTwo(a) {
        CompletableFuture<String> task1 = asyncTasks.doTaskTwo("1");
        CompletableFuture<String> task2 = asyncTasks.doTaskTwo("2");
        CompletableFuture<String> task3 = asyncTasks.doTaskTwo("3");
        
        CompletableFuture.allOf(task1, task2, task3).join();
        return ""; }}Copy the code

In the above code, there are two API interfaces, and the execution logic of both interfaces will split the execution process into three asynchronous tasks.

All right, think about it for a minute. Think about it. What would be the problem if this were implemented?


This code is fine if the API request concurrency is not high and if each task is processed fast enough. But if there is concurrency or some of the processes get stuck. These two interfaces that provide unrelated services may interact. For example, if the maximum number of threads in the current thread pool is 2, task1 and task2 in/API-1 are processed slowly and blocked. When a user calls apI-2, the service will block!

The reason for this is that by default, all asynchronous tasks created with @async share a common thread pool, so if some asynchronous tasks encounter performance problems, it will directly affect other asynchronous tasks.

To solve this problem, we need to do some thread pool isolation for asynchronous tasks, so that different asynchronous tasks do not affect each other.

Different asynchronous tasks are configured with different thread pools

Below, we come to actual operation!

Step 1: Initialize multiple thread pools like this:

@EnableAsync
@Configuration
public class TaskPoolConfig {

    @Bean
    public Executor taskExecutor1(a) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(2);
        executor.setQueueCapacity(10);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("executor-1-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }

    @Bean
    public Executor taskExecutor2(a) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(2);
        executor.setQueueCapacity(10);
        executor.setKeepAliveSeconds(60);
        executor.setThreadNamePrefix("executor-2-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        returnexecutor; }}Copy the code

Note: specially with executor. SetThreadNamePrefix set the thread name prefix, so that we can convenient to observe specific executive order later.

Step 2: Create an asynchronous task and specify the name of the thread pool to use

@Slf4j
@Component
public class AsyncTasks {

    public static Random random = new Random();

    @Async("taskExecutor1")
    public CompletableFuture<String> doTaskOne(String taskNo) throws Exception {
        log.info("Start task: {}", taskNo);
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("Complete task: {}, time: {} ms", taskNo, end - start);
        return CompletableFuture.completedFuture("Mission accomplished.");
    }

    @Async("taskExecutor2")
    public CompletableFuture<String> doTaskTwo(String taskNo) throws Exception {
        log.info("Start task: {}", taskNo);
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("Complete task: {}, time: {} ms", taskNo, end - start);
        return CompletableFuture.completedFuture("Mission accomplished."); }}Copy the code

TaskExecutor1 and taskExecutor2, as defined in the @async annotation, are the thread pool names. Since we didn’t specify the names of the two thread pool beans in step 1, we will use the method names by default, which are taskExecutor1 and taskExecutor2.

Step 3: Write a unit test to verify this, like this:

@Slf4j
@SpringBootTest
public class Chapter77ApplicationTests {

    @Autowired
    private AsyncTasks asyncTasks;

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

        Thread pool 1
        CompletableFuture<String> task1 = asyncTasks.doTaskOne("1");
        CompletableFuture<String> task2 = asyncTasks.doTaskOne("2");
        CompletableFuture<String> task3 = asyncTasks.doTaskOne("3");

        Thread pool 2
        CompletableFuture<String> task4 = asyncTasks.doTaskTwo("4");
        CompletableFuture<String> task5 = asyncTasks.doTaskTwo("5");
        CompletableFuture<String> task6 = asyncTasks.doTaskTwo("6");

        // Execute together
        CompletableFuture.allOf(task1, task2, task3, task4, task5, task6).join();

        long end = System.currentTimeMillis();

        log.info("All tasks completed, total time:" + (end - start) + "毫秒"); }}Copy the code

In the unit test above, a total of six asynchronous tasks were started, the first three using thread pool 1 and the last three using thread pool 2.

If the core thread is set to 2 and the maximum number of threads is set to 2, what will happen?

  1. The three tasks in thread pool 1, Task1 and Task2, get the execution thread first, and then Task3 enters the buffer queue because there are no threads to allocate
  2. The three tasks in thread pool 2, Task4 and Task5, get the execution thread first, and then Task6 enters the buffer queue because there are no threads to allocate
  3. Task Task3 will be executed after either Task1 or Task2 has completed
  4. Task Task6 will be executed after either Task4 or Task5 has completed

Once the analysis is complete, run a unit test to see if it looks like this:

Com. [executor - 1-1]. Didispace chapter77. AsyncTasks: start task: 1 / executor - 2-2 com. Didispace. Chapter77. AsyncTasks: Start task: 5] [executor - 2-1 com. Didispace. Chapter77. AsyncTasks: start task: 4 [executor - 1-2] com. Didispace. Chapter77. AsyncTasks: Start task: 2 com [executor - 2-1]. Didispace. Chapter77. AsyncTasks: To complete the task: 4, time-consuming: 4532 milliseconds com [executor - 2-1]. Didispace. Chapter77. AsyncTasks: Start the task: 6 [executor - 1-2] com. Didispace. Chapter77. AsyncTasks: To complete the task: 2, time-consuming: 6890 milliseconds [executor - 1-2] com. Didispace. Chapter77. AsyncTasks: Start the task: 3 [executor - 2-2] com. Didispace. Chapter77. AsyncTasks: To complete the task: 5, time-consuming: 7523 milliseconds [executor - 1-2] com. Didispace. Chapter77. AsyncTasks: To complete the task: 3, time-consuming: 1579 milliseconds com [executor - 1-1]. The didispace. Chapter77. AsyncTasks: To complete the task: 1, time-consuming: 9178 milliseconds com [executor - 2-1]. The didispace. Chapter77. AsyncTasks: To complete the task: 6, time-consuming: 8212 milliseconds [main] C.D.C. hapter77. Chapter77ApplicationTests: mission complete, total time: 12762 millisecondsCopy the code

From: https://blog.didispace.com/spring-boot-learning-2-7-7/