4

Through the last article: Configuring @Async Asynchronous Task Thread Pool , you should have understood that there is a thread pool behind the execution of asynchronous tasks to manage execution 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 excessive use of resources. In addition to the configuration of the default thread pool, there is another type of scenario, which is also very common, that is, thread pool isolation in the case of multitasking.

What is the isolation of the thread pool, and why should it be isolated

Maybe some friends still don't know much about what is the isolation of the thread pool, why do you want to isolate ? . So, let's take a look at the following scenario case first:

@RestController
public class HelloController {

    @Autowired
    private AsyncTasks asyncTasks;
        
    @GetMapping("/api-1")
    public String taskOne() {
        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() {
        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 "";
    }
    
}

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

Okay, think about it for a minute, think about it. Will there be any problems if it is implemented this way?


The above code is no problem when the API request concurrency is not high, and if the processing speed of each task is fast enough. But if it comes up concurrently or some of the processes are hindered. These two interfaces that provide unrelated services may affect each other. For example: assuming that the maximum number of threads configured in the current thread pool is 2, the processing speed of task1 and task2 in the /api-1 interface is very slow and blocked; then, when the user calls the api-2 interface, the service It will also block!

The reason for this kind of scene is: by default, all @Async share a thread pool, so when some asynchronous tasks encounter performance problems, it will directly affect other asynchronous tasks.

In order to solve this problem, we need to do a certain thread pool isolation for asynchronous tasks, so that different asynchronous tasks do not affect each other.

Configure different thread pools for different asynchronous tasks

Now, let's do it!

first step : Initialize multiple thread pools, such as the following:

@EnableAsync
@Configuration
public class TaskPoolConfig {

    @Bean
    public Executor taskExecutor1() {
        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() {
        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());
        return executor;
    }
}
executor.setThreadNamePrefix is specially used here to set the prefix of the thread name, so that it is convenient to observe the specific execution sequence later.

second step : Create an asynchronous task, and specify the name of the thread pool to be used

@Slf4j
@Component
public class AsyncTasks {

    public static Random random = new Random();

    @Async("taskExecutor1")
    public CompletableFuture<String> doTaskOne(String taskNo) throws Exception {
        log.info("开始任务:{}", taskNo);
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("完成任务:{},耗时:{} 毫秒", taskNo, end - start);
        return CompletableFuture.completedFuture("任务完成");
    }

    @Async("taskExecutor2")
    public CompletableFuture<String> doTaskTwo(String taskNo) throws Exception {
        log.info("开始任务:{}", taskNo);
        long start = System.currentTimeMillis();
        Thread.sleep(random.nextInt(10000));
        long end = System.currentTimeMillis();
        log.info("完成任务:{},耗时:{} 毫秒", taskNo, end - start);
        return CompletableFuture.completedFuture("任务完成");
    }

}

Here @Async annotation defined taskExecutor1 and taskExecutor2 is the name of the thread pool. Since in the first step, we did not write the names of the two thread pool beans, the method names will be used by default, that is, taskExecutor1 and taskExecutor2 .

third step : Write a unit test to verify, such as the following:

@Slf4j
@SpringBootTest
public class Chapter77ApplicationTests {

    @Autowired
    private AsyncTasks asyncTasks;

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

        // 线程池1
        CompletableFuture<String> task1 = asyncTasks.doTaskOne("1");
        CompletableFuture<String> task2 = asyncTasks.doTaskOne("2");
        CompletableFuture<String> task3 = asyncTasks.doTaskOne("3");

        // 线程池2
        CompletableFuture<String> task4 = asyncTasks.doTaskTwo("4");
        CompletableFuture<String> task5 = asyncTasks.doTaskTwo("5");
        CompletableFuture<String> task6 = asyncTasks.doTaskTwo("6");

        // 一起执行
        CompletableFuture.allOf(task1, task2, task3, task4, task5, task6).join();

        long end = System.currentTimeMillis();

        log.info("任务全部完成,总耗时:" + (end - start) + "毫秒");
    }

}

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

Do not execute it first, and analyze the execution situation according to the core thread 2 and the maximum number of threads 2 set.

  1. For the three tasks of thread pool 1, task1 and task2 will get the execution thread first, and then task3 will enter the buffer queue because there is no thread that can be allocated.
  2. For the three tasks of thread pool 2, task4 and task5 will get the execution thread first, and then task6 will enter the buffer queue because there is no thread that can be allocated.
  3. Task task3 will start execution after task1 or task2 is completed
  4. Task task6 will start execution after task4 or task5 is completed

After the analysis is complete, perform the next unit test to see if it is like this:

2021-09-15 23:45:11.369  INFO 61670 --- [   executor-1-1] com.didispace.chapter77.AsyncTasks       : 开始任务:1
2021-09-15 23:45:11.369  INFO 61670 --- [   executor-2-2] com.didispace.chapter77.AsyncTasks       : 开始任务:5
2021-09-15 23:45:11.369  INFO 61670 --- [   executor-2-1] com.didispace.chapter77.AsyncTasks       : 开始任务:4
2021-09-15 23:45:11.369  INFO 61670 --- [   executor-1-2] com.didispace.chapter77.AsyncTasks       : 开始任务:2
2021-09-15 23:45:15.905  INFO 61670 --- [   executor-2-1] com.didispace.chapter77.AsyncTasks       : 完成任务:4,耗时:4532 毫秒
2021-09-15 23:45:15.905  INFO 61670 --- [   executor-2-1] com.didispace.chapter77.AsyncTasks       : 开始任务:6
2021-09-15 23:45:18.263  INFO 61670 --- [   executor-1-2] com.didispace.chapter77.AsyncTasks       : 完成任务:2,耗时:6890 毫秒
2021-09-15 23:45:18.263  INFO 61670 --- [   executor-1-2] com.didispace.chapter77.AsyncTasks       : 开始任务:3
2021-09-15 23:45:18.896  INFO 61670 --- [   executor-2-2] com.didispace.chapter77.AsyncTasks       : 完成任务:5,耗时:7523 毫秒
2021-09-15 23:45:19.842  INFO 61670 --- [   executor-1-2] com.didispace.chapter77.AsyncTasks       : 完成任务:3,耗时:1579 毫秒
2021-09-15 23:45:20.551  INFO 61670 --- [   executor-1-1] com.didispace.chapter77.AsyncTasks       : 完成任务:1,耗时:9178 毫秒
2021-09-15 23:45:24.117  INFO 61670 --- [   executor-2-1] com.didispace.chapter77.AsyncTasks       : 完成任务:6,耗时:8212 毫秒
2021-09-15 23:45:24.117  INFO 61670 --- [           main] c.d.chapter77.Chapter77ApplicationTests  : 任务全部完成,总耗时:12762毫秒

Well, that's all for today's study! If you encounter difficulties in the learning process? You can join our super high-quality Spring technical exchange group , participate in exchanges and discussions, and learn and progress better! More Spring Boot tutorials can be clicked directly! , welcome to collect and forward support!

Code example

The complete project of this article can be viewed in chapter7-7 project 2.x directory in the following warehouse:

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, share knowledge and thoughts that can’t be seen elsewhere

程序猿DD
2.2k 声望2.8k 粉丝

作品:《Spring Cloud微服务实战》、SpringForAll社区、OpenWrite、Youtube中文配音