一、默认行为:不配置线程池的风险
1. @Async 的默认线程池
当使用 @Async 注解时,若未显式指定线程池,Spring Boot 会使用 SimpleAsyncTaskExecutor。
特点:
-
每个异步任务都会创建一个新线程。
-
线程不重用,任务结束后线程销毁。
潜在问题:
@Async
public void asyncTask() {
// 耗时操作
}
- 资源耗尽:高并发场景下频繁创建线程,导致内存和 CPU 资源耗尽。
- 性能下降:线程创建和销毁的开销显著,影响系统吞吐量。
2. @Scheduled 的默认线程池
当使用 @Scheduled 注解时,默认使用 Executors.newSingleThreadScheduledExecutor()。
特点:
-
单线程执行所有定时任务。
-
任务按顺序执行,无并发。
潜在问题:
@Scheduled(fixedRate = 1000)
public void scheduledTask() {
// 耗时操作
}
- 任务阻塞:某个任务执行时间过长,后续任务会被延迟。
- 调度失效:单线程崩溃会导致所有定时任务终止。
二、问题场景:未配置线程池的典型 Bug
1. 异步任务资源耗尽
场景:某个接口调用触发大量异步任务。
现象:
- 日志中出现
OutOfMemoryError: unable to create new native thread。 - CPU 使用率飙升,系统响应变慢。
原因: SimpleAsyncTaskExecutor无限制创建线程,耗尽系统资源。
2. 定时任务调度延迟
场景:多个定时任务共享单线程。
现象:
-
任务实际执行间隔远大于配置的
fixedRate。 -
监控显示任务排队堆积。
代码示例:
@Scheduled(fixedRate = 1000)
public void task1() {
Thread.sleep(2000); // 阻塞 2 秒
}
@Scheduled(fixedRate = 1000)
public void task2() {
// 被 task1 阻塞,延迟执行
}
3. 线程上下文污染
场景:异步任务依赖 ThreadLocal 上下文(如用户认证信息)。
现象:
- 异步任务中获取不到主线程的 ThreadLocal 数据。
原因: - 默认线程池每次创建新线程,ThreadLocal 数据未传递。
三、解决方案:显式配置线程池
1. 配置异步任务线程池
步骤:
-
定义线程池 Bean:
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("customAsyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(8);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("Async-");
executor.initialize();
return executor;
}
}
-
在异步方法中指定线程池:
@Async("customAsyncExecutor")
public void asyncTask() {
// 任务逻辑
}
避坑指南:
- 避免在同一个类中调用
@Async方法(需通过代理调用)。
2. 配置定时任务线程池
步骤:
-
定义线程池 Bean:
@Configuration
@EnableScheduling
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(Executors.newScheduledThreadPool(4));
}
}
-
定时任务自动使用线程池:
@Scheduled(fixedRate = 1000)
public void scheduledTask() {
// 任务逻辑
}
避坑指南:
- 确保
@Scheduled方法执行时间小于调度间隔。
3. 线程上下文传递
场景:异步任务需要获取主线程的上下文(如用户信息)。
解决方案:使用 TaskDecorator 传递 ThreadLocal。
@Bean("customAsyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// ... 其他配置
executor.setTaskDecorator(new ContextCopyingDecorator());
return executor;
}
public class ContextCopyingDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
// 捕获主线程上下文
Map<String, Object> context = ThreadLocalContext.getContext();
return () -> {
try {
ThreadLocalContext.setContext(context);
runnable.run();
} finally {
ThreadLocalContext.clear();
}
};
}
}
四、监控与调优
1. 监控线程池状态
通过 Actuator 暴露线程池指标:
management:
endpoints:
web:
exposure:
include: metrics,threadpools
访问 /actuator/metrics 查看线程池活跃线程数、队列大小等。
2. 动态调整线程池参数
结合配置中心(如 Spring Cloud Config),动态修改线程池参数:
@RefreshScope
@Bean("customAsyncExecutor")
public Executor asyncExecutor(
@Value("${async.core-pool-size:4}") int corePoolSize,
@Value("${async.max-pool-size:8}") int maxPoolSize
) {
// 初始化线程池
}
五、常见错误与修复
1. 异步方法不生效
原因:未在主类或配置类添加 @EnableAsync。
修复:
@SpringBootApplication
@EnableAsync // 添加此注解
public class Application { ... }
2. 定时任务阻塞
现象:多个 @Scheduled 任务串行执行。
修复:显式配置多线程池,或为任务指定不同线程池。
3. 线程池拒绝策略不当
现象:日志中出现 RejectedExecutionException。
修复:配置合理的拒绝策略:
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());