Using Spring's @Async Annotation: Thread Pool Configuration and Custom Implementations
This article explains the purpose and usage rules of Spring's @Async annotation, compares built‑in executors, demonstrates how to enable @Async, and provides detailed examples of custom thread‑pool configurations, multiple pool management, and important source‑code analysis for robust asynchronous processing in Java backend applications.
This article describes the @Async annotation and its usage rules within the Spring framework, focusing on how to apply it for asynchronous method calls without covering underlying principles or source‑code analysis.
Introduction
Application Scenarios
Synchronous: The whole processing flow executes sequentially, and each step must finish before the next begins.
Asynchronous: The caller sends the invocation command and continues execution without waiting for the callee to finish.
For example, when a process needs to call methods A, B, and C in order, if B is asynchronous the system executes A, then triggers B and immediately proceeds to C; the overall process finishes only after C completes.
In Java, such scenarios are usually handled by creating independent threads so that the main thread can continue while worker threads perform the asynchronous logic.
Spring‑Provided Executors
SimpleAsyncTaskExecutor : Not a real thread pool; it creates a new thread for each task.
SyncTaskExecutor : Executes tasks synchronously; suitable when multithreading is not required.
ConcurrentTaskExecutor : An adapter for Executor ; not recommended unless ThreadPoolTaskExecutor does not meet requirements.
SimpleThreadPoolTaskExecutor : Used by Quartz; shared between Quartz and non‑Quartz tasks.
ThreadPoolTaskExecutor : The most common choice; essentially a wrapper around java.util.concurrent.ThreadPoolExecutor .
Asynchronous Method Types
Void return type – simplest async call.
Methods with parameters – async calls can accept arguments.
Methods returning Future – allow result retrieval.
Enabling @Async in Spring
// Java‑based configuration
@Configuration
@EnableAsync
public class SpringAsyncConfig { ... }
// Spring Boot style
@EnableAsync
@EnableTransactionManagement
public class SettlementApplication {
public static void main(String[] args) {
SpringApplication.run(SettlementApplication.class, args);
}
}@Async Default Thread Pool
When @Async is used without specifying a pool name, Spring falls back to SimpleAsyncTaskExecutor, which creates a new thread for each task and can cause OutOfMemoryError under heavy load.
The executor can be limited via the concurrencyLimit property; a value ≥ 0 enables throttling, while the default -1 disables it.
Custom Thread Pool for @Async
Custom pools give finer control over size, queue capacity, rejection policy, and thread naming. Only one custom pool can replace the default; multiple pools require explicit naming via @Async("poolName").
Common ways to provide a custom pool:
Implement AsyncConfigurer and expose a ThreadPoolTaskExecutor bean.
Extend AsyncConfigurerSupport and override getAsyncExecutor().
Define a bean of type TaskExecutor (or Executor) with the default bean name taskExecutor.
@Configuration
public class AsyncConfiguration implements AsyncConfigurer {
@Bean("kingAsyncExecutor")
public ThreadPoolTaskExecutor executor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(10);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setThreadNamePrefix("kingDeeAsyncExecutor-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(5);
executor.initialize();
return executor;
}
@Override
public Executor getAsyncExecutor() {
return executor();
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> ErrorLogger.getInstance()
.log(String.format("执行异步任务'%s'", method), ex);
}
} @Configuration
@EnableAsync
public class SpringAsyncConfigurer extends AsyncConfigurerSupport {
@Bean
public ThreadPoolTaskExecutor asyncExecutor() {
ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
threadPool.setCorePoolSize(3);
threadPool.setMaxPoolSize(3);
threadPool.setWaitForTasksToCompleteOnShutdown(true);
threadPool.setAwaitTerminationSeconds(60 * 15);
return threadPool;
}
@Override
public Executor getAsyncExecutor() {
return asyncExecutor();
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> ErrorLogger.getInstance()
.log(String.format("执行异步任务'%s'", method), ex);
}
}Defining a bean named taskExecutor (or using the constant AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME) also registers a default pool that Spring will pick up.
@EnableAsync
@Configuration
public class TaskPoolConfig {
@Bean(name = AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME)
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("taskExecutor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
@Bean(name = "new_task")
public Executor anotherTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("taskExecutor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
}Multiple pools can be used by specifying the pool name in the annotation, e.g., @Async("new_task").
Important Source‑Code Snippets
Spring obtains the executor via getAsyncExecutor() from AsyncConfigurer. If none is provided, it falls back to the default bean named taskExecutor, and finally to the internal SimpleAsyncTaskExecutor implementation.
Images in the original article illustrate the lookup process and the fallback hierarchy.
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.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.
