Spring Boot Scheduled Tasks: Serial vs Parallel Execution and Custom Thread Pool Configuration
This article demonstrates how to configure Spring Boot @Scheduled tasks for both serial and parallel execution, introduces @Async for asynchronous processing, shows how to define custom local and global thread pools, and explains exception handling for asynchronous tasks, providing complete code examples.
Project Setup
The project uses JDK 1.8+ with Spring Boot. The main application class is annotated with @EnableScheduling and a ScheduledTask component is created to define two simple scheduled methods that print the thread name and a timestamp.
@Component
public class ScheduledTask {
@Scheduled(cron = "0/1 * * * * ?")
public void scheduledTask1() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + "---scheduledTask1 " + System.currentTimeMillis());
}
@Scheduled(cron = "0/1 * * * * ?")
public void scheduledTask2() {
System.out.println(Thread.currentThread().getName() + "---scheduledTask2 " + System.currentTimeMillis());
}
}1. Serial Execution of Multiple Tasks
When the two tasks run at the same cron expression, the console logs show no deterministic order – the tasks are executed in a random sequence, indicating that serial scheduled tasks have no built‑in priority.
If one task blocks (e.g., by entering an infinite loop), the other task is also delayed because the default scheduler runs tasks sequentially.
@Component
public class ScheduledTask {
@Scheduled(cron = "0/1 * * * * ?")
public void scheduledTask1() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + "---scheduledTask1 " + System.currentTimeMillis());
while (true) {
Thread.sleep(5000);
}
}
@Scheduled(cron = "0/1 * * * * ?")
public void scheduledTask2() {
System.out.println(Thread.currentThread().getName() + "---scheduledTask2 " + System.currentTimeMillis());
}
}2. Parallel Execution with @Async
To achieve parallel execution, @EnableAsync is added to the main class and each scheduled method is annotated with @Async. The default executor is SimpleAsyncTaskExecutor, which creates a new thread for every invocation.
@Component
public class ScheduledTask {
@Scheduled(cron = "0/1 * * * * ?")
@Async
public void scheduledTask1() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + "---scheduledTask1 " + System.currentTimeMillis());
}
@Scheduled(cron = "0/1 * * * * ?")
@Async
public void scheduledTask2() {
System.out.println(Thread.currentThread().getName() + "---scheduledTask2 " + System.currentTimeMillis());
}
}Logs confirm that each task runs in a separate thread named SimpleAsyncTaskExecutor-…, which is not recommended for production because threads are never reused.
3. Custom Thread Pool
3.1 Local Thread Pool
A local thread pool is defined and referenced explicitly with @Async("asyncTaskExecutor"). The configuration sets core size, max size, queue capacity, rejection policy, and graceful shutdown settings.
@Component
public class AsyncTaskExecutorConfig {
@Bean("asyncTaskExecutor")
public AsyncTaskExecutor asyncTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setThreadNamePrefix("defineAsyncTask-");
executor.setCorePoolSize(3);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
}The scheduled methods are updated to use the custom pool:
@Component
public class ScheduledTask {
@Scheduled(cron = "0/1 * * * * ?")
@Async("asyncTaskExecutor")
public void scheduledTask1() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + "---scheduledTask1 " + System.currentTimeMillis());
}
@Scheduled(cron = "0/1 * * * * ?")
@Async // uses default SimpleAsyncTaskExecutor
public void scheduledTask2() {
System.out.println(Thread.currentThread().getName() + "---scheduledTask2 " + System.currentTimeMillis());
}
}3.2 Global Thread Pool
A global thread pool can be provided by implementing AsyncConfigurer (or extending AsyncConfigurerSupport). This pool is used by all @Async methods that do not specify a particular executor.
@Configuration
public class AsyncGlobalConfig extends AsyncConfigurerSupport {
private static final String THREAD_PREFIX = "defineGlobalAsync-";
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setThreadNamePrefix(THREAD_PREFIX);
executor.setCorePoolSize(3);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setKeepAliveSeconds(60);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
}When the global pool is in place, tasks without an explicit executor run on this pool, while tasks that specify "asyncTaskExecutor" continue to use the local pool.
4. Exception Handling for Asynchronous Tasks
For submit() based async calls, Future.get() propagates exceptions to the caller. For execute() (void‑returning) tasks, exceptions are lost unless a custom AsyncUncaughtExceptionHandler is provided.
static class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
@Override
public void handleUncaughtException(Throwable e, Method method, Object... args) {
// custom exception handling logic
}
}The global configuration overrides getAsyncUncaughtExceptionHandler() to return the custom handler:
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new CustomAsyncExceptionHandler();
}With these configurations, scheduled tasks can run safely in parallel, use efficient thread reuse, and have a unified strategy for handling runtime exceptions.
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.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
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.
