Isolating Spring @Async Thread Pools to Prevent Task Interference
This tutorial explains why the default shared thread pool for @Async tasks can cause unrelated services to block each other, and shows step‑by‑step how to configure separate thread pools for different async tasks in Spring Boot, complete with code examples and a unit test.
In the previous article we introduced how @Async tasks are executed by a thread pool. To avoid excessive resource usage we must configure the pool, but when multiple independent services share the same pool, a slow task in one service can block the other.
What is thread‑pool isolation and why is it needed?
When several asynchronous tasks use the default shared pool, a performance problem in one task can affect all other tasks because they compete for the same threads.
@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 "";
}
}Both APIs split their work into three asynchronous tasks. If the thread pool’s maximum size is small (e.g., 2), slow tasks from one API can occupy all threads, causing the other API to block.
Configure different thread pools for different async tasks
Step 1 – Initialize multiple thread pools:
@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;
}
}Note: Setting executor.setThreadNamePrefix makes it easy to see which pool a thread belongs to.
Step 2 – Create async tasks and bind them to the pools:
@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("任务完成");
}
}The names taskExecutor1 and taskExecutor2 correspond to the bean methods defined in the configuration class.
Step 3 – Write a unit test to verify isolation:
@Slf4j
@SpringBootTest
public class Chapter77ApplicationTests {
@Autowired
private AsyncTasks asyncTasks;
@Test
public void test() throws Exception {
long start = System.currentTimeMillis();
// pool 1
CompletableFuture<String> task1 = asyncTasks.doTaskOne("1");
CompletableFuture<String> task2 = asyncTasks.doTaskOne("2");
CompletableFuture<String> task3 = asyncTasks.doTaskOne("3");
// pool 2
CompletableFuture<String> task4 = asyncTasks.doTaskTwo("4");
CompletableFuture<String> task5 = asyncTasks.doTaskTwo("5");
CompletableFuture<String> task6 = asyncTasks.doTaskTwo("6");
// run all
CompletableFuture.allOf(task1, task2, task3, task4, task5, task6).join();
long end = System.currentTimeMillis();
log.info("任务全部完成,总耗时:" + (end - start) + "毫秒");
}
}The test launches six async tasks: three use pool 1 and three use pool 2. With each pool configured with a core size of 2, the expected execution order is:
In pool 1, task1 and task2 start immediately; task3 waits in the queue.
In pool 2, task4 and task5 start immediately; task6 waits in the queue.
When any running task finishes, the queued task (task3 or task6) is picked up.
Running the test produces logs similar to the following, confirming that tasks from different pools do not block each other:
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
... (log continues) ...
2021-09-15 23:45:24.117 INFO 61670 --- [ main] c.d.chapter77.Chapter77ApplicationTests : 任务全部完成,总耗时:12762毫秒By isolating @Async tasks into dedicated thread pools, you prevent a slow or blocked task in one service from degrading the performance of unrelated services.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
