Spring Boot 中基于线程池的异步任务处理
在 Spring Boot 应用中,可以通过 @EnableAsync 和 @Async 注解实现异步任务的多线程执行。这种方式能够有效提升系统响应能力,尤其适用于耗时操作如文件处理、远程调用等场景。
启用异步支持的基本步骤
- 在配置类上添加
@EnableAsync启用异步功能。 - 定义一个或多个自定义线程池,推荐优于使用默认线程池。
- 在目标方法上标注
@Async,确保该方法为 public 且由外部类调用。
单一任务类型与共享线程池
当应用中仅存在一种异步任务时,可配置一个通用线程池供所有异步方法共用。
@Configuration
@EnableAsync
public class TaskExecutionConfig {
@Bean("sharedExecutor")
public Executor buildSharedPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(30);
executor.setQueueCapacity(200);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("Worker-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
executor.initialize();
return executor;
}
}
服务类中声明异步方法:
@Service
public class BackgroundTaskService {
private static final Logger log = LoggerFactory.getLogger(BackgroundTaskService.class);
@Async("sharedExecutor")
public void executeTask(int taskId) throws InterruptedException {
log.info("任务 {} 开始执行", taskId);
Thread.sleep(8000); // 模拟耗时操作
log.info("任务 {} 执行完成", taskId);
}
}
控制器触发批量任务:
@RestController
@RequestMapping("/jobs")
public class JobController {
@Autowired
private BackgroundTaskService taskService;
@GetMapping("/run")
public ResponseEntity<String> startJobs() {
for (int i = 1; i <= 15; i++) {
taskService.executeTask(i);
}
return ResponseEntity.ok("任务已提交");
}
}
此时,15 个任务将由线程池统一调度:前 10 个直接分配线程,后 5 个进入队列等待空闲线程释放。
多类型任务隔离执行
若系统包含不同性质的任务(如 I/O 密集型和 CPU 密集型),建议分配独立线程池以避免资源争抢。
@Configuration
@EnableAsync
public class DedicatedPoolConfig {
@Bean("ioPool")
public Executor buildIoBoundPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(8);
executor.setMaxPoolSize(40);
executor.setQueueCapacity(150);
executor.setThreadNamePrefix("IoWorker-");
executor.setKeepAliveSeconds(120);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
@Bean("cpuPool")
public Executor buildCpuBoundPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(8);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("CpuWorker-");
executor.setKeepAliveSeconds(60);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
executor.initialize();
return executor;
}
}
任务服务分别绑定不同线程池:
@Service
public class SpecializedTaskService {
private static final Logger log = LoggerFactory.getLogger(SpecializedTaskService.class);
@Async("ioPool")
public void handleFileUpload(int jobId) throws InterruptedException {
log.info("I/O 任务 {} 启动", jobId);
Thread.sleep(12000);
log.info("I/O 任务 {} 完成", jobId);
}
@Async("cpuPool")
public void processData(int jobId) throws InterruptedException {
log.info("计算任务 {} 启动", jobId);
Thread.sleep(7000);
log.info("计算任务 {} 完成", jobId);
}
}
控制器同时触发两类任务:
@RestController
@RequestMapping("/tasks")
public class TaskController {
@Autowired
private SpecializedTaskService service;
@GetMapping("/launch")
public String launchMixedTasks() {
for (int i = 1; i <= 6; i++) {
service.handleFileUpload(i);
service.processData(i);
}
return "混合任务已提交";
}
}
访问接口后,日志显示两类任务按各自线程池策略并行执行,互不影响。
关键注意事项
- 调用隔离:@Async 方法必须被外部 Bean 调用,同一类内直接调用不会触发代理机制,导致异步失效。
- 返回值限制:异步方法若需返回结果,应使用
Future<T>或CompletableFuture<T>包装。 - 异常处理:未捕获的异常可能导致任务中断,建议在方法内部进行 try-catch 处理。
线程池参数说明与执行逻辑
| 参数 | 说明 |
|---|---|
| corePoolSize | 核心线程数,常驻内存的最小工作线程数量 |
| maxPoolSize | 最大线程上限,超出队列容量后允许创建的额外线程数 |
| queueCapacity | 待处理任务缓冲区大小 |
| keepAliveSeconds | 非核心线程空闲存活时间 |
| rejectedExecutionHandler | 拒绝策略,常见有 AbortPolicy(抛异常)、CallerRunsPolicy(由调用者线程执行)等 |
任务调度顺序:新任务优先分配给空闲核心线程 → 若无空闲核心线程则入队 → 队列满时启动非核心线程 → 达到最大线程数且队列满,则触发拒绝策略。
例如:核心线程=2,最大线程=4,队列容量=5。
- ≤2 个任务:仅使用核心线程
- 3~7 个任务:2 核心 + (1~5) 入队
- 8~9 个任务:2 核心 + 5 队列 + (1~2) 新建线程
- ≥10 个任务:超出容量,执行拒绝策略