Careeroza
Configuring Thread Pools
Your Own Executor
@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
@Bean(name = "notificationExecutor")
public ThreadPoolTaskExecutor notificationExecutor() {
ThreadPoolTaskExecutor ex = new ThreadPoolTaskExecutor();
ex.setCorePoolSize(4);
ex.setMaxPoolSize(16);
ex.setQueueCapacity(500);
ex.setThreadNamePrefix("notify-");
ex.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
ex.setWaitForTasksToCompleteOnShutdown(true);
ex.setAwaitTerminationSeconds(30);
ex.initialize();
return ex;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) ->
LoggerFactory.getLogger("async").error("Async {} failed: {}", method.getName(), ex.getMessage());
}
}
// pick the pool by bean name
@Async("notificationExecutor")
public void sendOrderEmail(String email, Long orderId) { ... }Or Just Properties
Example
Spring Boot's default executor can be tuned without code:
application.yml
spring:
task:
execution:
thread-name-prefix: shopease-async-
pool:
core-size: 8
max-size: 32
queue-capacity: 1000
shutdown:
await-termination: true
await-termination-period: 30sHow a Pool Grows
A ThreadPoolExecutor fills core threads first, then the queue, and only then creates extra threads up to max. When everything is full the rejection policy runs.
Example
Watch a tiny pool (core 1, queue 2, max 2) receive 5 long-running tasks with the default AbortPolicy:
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws Exception {
CountDownLatch release = new CountDownLatch(1);
ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 30, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2), new ThreadPoolExecutor.AbortPolicy());
for (int i = 1; i <= 5; i++) {
int task = i;
try {
pool.execute(() -> { try { release.await(); } catch (InterruptedException e) { } });
System.out.printf("task %d accepted -> threads=%d, queued=%d%n",
task, pool.getPoolSize(), pool.getQueue().size());
} catch (RejectedExecutionException e) {
System.out.println("task " + task + " REJECTED (threads and queue are full)");
}
}
release.countDown();
pool.shutdown();
pool.awaitTermination(5, TimeUnit.SECONDS);
System.out.println("completed tasks: " + pool.getCompletedTaskCount());
}
}Output
task 1 accepted -> threads=1, queued=0 task 2 accepted -> threads=1, queued=1 task 3 accepted -> threads=1, queued=2 task 4 accepted -> threads=2, queued=2 task 5 REJECTED (threads and queue are full) completed tasks: 4
Tip: CallerRunsPolicy is a safer choice for web apps: instead of failing, the request thread runs the task itself, which naturally slows down new work.