Using @Async Annotation in Spring: Application Scenarios, Built‑in Thread Pools, and Custom Thread‑Pool Configuration
This article explains the @Async annotation in Spring, how it enables asynchronous method execution, compares the built‑in executors, shows how to configure custom thread pools via AsyncConfigurer or AsyncConfigurerSupport, and provides code examples for void, Future and CompletableFuture based async methods.
The article introduces the @Async annotation in the Spring framework, describing its rule‑based usage to execute methods asynchronously by submitting tasks to a Spring TaskExecutor, while the caller returns immediately.
In real projects it is recommended to use a custom thread pool for @Async calls; a common approach is to re‑implement the AsyncConfigurer interface.
Spring provides several built‑in executors: SimpleAsyncTaskExecutor: creates a new thread for each task, no reuse. SyncTaskExecutor: synchronous execution, no threading. ConcurrentTaskExecutor: an adapter, not recommended unless necessary. SimpleThreadPoolTaskExecutor: used by Quartz, shared between Quartz and non‑Quartz code. ThreadPoolTaskExecutor: the most common choice, a wrapper around java.util.concurrent.ThreadPoolExecutor.
Three typical async method signatures are covered:
Void return type (no result handling).
Methods with parameters (still void).
Methods returning Future<T> (or CompletableFuture<T>) for result retrieval.
Enabling @Async in Spring can be done via Java configuration:
@Configuration
@EnableAsync
public class SpringAsyncConfig { ... }or in a Spring Boot application:
@EnableAsync
@EnableTransactionManagement
public class SettlementApplication {
public static void main(String[] args) {
SpringApplication.run(SettlementApplication.class, args);
}
}The default executor for @Async is SimpleAsyncTaskExecutor, which creates a new thread per task and may cause OOM; it can be limited with the concurrencyLimit property.
Custom thread‑pool configuration examples:
@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);
}
}Alternatively, extend AsyncConfigurerSupport:
@Configuration
@EnableAsync
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 default TaskExecutor bean also replaces the built‑in executor; the bean name can be the constant AsyncExecutionAspectSupport.DEFAULT_TASK_EXECUTOR_BEAN_NAME or any name you reference with @Async("beanName"):
@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 newTaskExecutor() {
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;
}When multiple thread pools exist, specify the target pool with @Async("new_task").
The article also shows source‑code snippets for async methods:
@Async
public void asyncInvokeWithException(String s) {
log.info("asyncInvokeWithParameter, parementer={}", s);
throw new IllegalArgumentException(s);
} @Async
public Future<String> asyncInvokeReturnFuture(int i) {
log.info("asyncInvokeReturnFuture, parementer={}", i);
Future<String> future;
try {
Thread.sleep(1000 * 1);
future = new AsyncResult<String>("success:" + i);
throw new IllegalArgumentException("a");
} catch (InterruptedException e) {
future = new AsyncResult<String>("error");
} catch (IllegalArgumentException e) {
future = new AsyncResult<String>("error-IllegalArgumentException");
}
return future;
}Finally, the article warns that using the default SimpleAsyncTaskExecutor without limits can exhaust memory, and cites Alibaba’s Java development guidelines recommending explicit ThreadPoolExecutor configurations instead of the convenience Executors factories.
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.
Architect's Tech Stack
Java backend, microservices, distributed systems, containerized programming, and more.
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.
