Spring Boot 优雅实现异步调用:从入门到自定义线程池与异常处理
在 Spring Boot 项目里,同步接口经常会被慢接口拖垮响应速度。异步调用可以把耗时任务丢到独立线程,让主线程快速返回,提升接口吞吐量与用户体验。
这篇文章用最清晰、最工程化的方式,带你从零学会 Spring Boot 异步开发,包含默认使用、自定义线程池、异常处理、避坑要点。
一、先搞懂:同步 vs 异步
- 同步:任务串行执行,必须等上一个完成才能走下一个,主线程阻塞。
- 异步:任务提交给独立线程执行,主线程不用等,直接返回。
适用场景:
- 发送短信、推送消息
- 记录日志、上报埋点
- 调用第三方慢接口
- 批量数据处理
二、Spring Boot 异步最简实现(3步搞定)
Spring 提供@Async+@EnableAsync开箱即用。
1. 启动类/配置类开启异步
@SpringBootApplication @EnableAsync // 开启异步 public class AsyncApplication { public static void main(String[] args) { SpringApplication.run(AsyncApplication.class, args); } }2. 编写异步 Service
规则
- 方法必须是
public - 不能是本类内部调用(AOP 代理限制)
- 返回值只能是
void或Future<T>
@Service @Slf4j public class AsyncServiceImpl { // 无返回值异步 @Async public void asyncVoidTask() throws InterruptedException { Thread.sleep(3000); log.info("异步任务执行,线程:{}", Thread.currentThread().getName()); } // 带返回值异步 @Async public Future<String> asyncFutureTask() throws InterruptedException { Thread.sleep(5000); log.info("异步任务执行,线程:{}", Thread.currentThread().getName()); return new AsyncResult<>("任务执行完成"); } }3. Controller 调用
@RestController @Slf4j public class AsyncController { @Autowired private AsyncServiceImpl asyncService; @GetMapping("/async/void") public String testVoid() { long start = System.currentTimeMillis(); log.info("主线程:{}", Thread.currentThread().getName()); asyncService.asyncVoidTask(); return "耗时:" + (System.currentTimeMillis() - start) + "ms"; } @GetMapping("/async/future") public String testFuture() throws ExecutionException, InterruptedException { long start = System.currentTimeMillis(); Future<String> future = asyncService.asyncFutureTask(); // 阻塞获取结果 String result = future.get(); return result + ",总耗时:" + (System.currentTimeMillis() - start) + "ms"; } }运行结果
/async/void:立即返回,耗时几毫秒,异步任务后台执行。/async/future:会阻塞 5 秒,但依然是异步执行。
三、自定义异步线程池(生产必须用)
Spring 默认用SimpleAsyncTaskExecutor,不重用线程,高并发会OOM。生产环境必须自定义线程池。
@Configuration public class AsyncConfig implements AsyncConfigurer { @Override @Bean("asyncTaskExecutor") public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 核心线程数 executor.setCorePoolSize(5); // 最大线程数 executor.setMaxPoolSize(10); // 队列容量 executor.setQueueCapacity(200); // 线程名前缀 executor.setThreadNamePrefix("async-task-"); // 关机时等待任务完成 executor.setWaitForTasksToCompleteOnShutdown(true); // 等待时长 executor.setAwaitTerminationSeconds(60); // 拒绝策略 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } }使用时指定线程池:
@Async("asyncTaskExecutor") public void asyncVoidTask() { ... }四、自定义异步异常处理器
异步方法里抛异常,主线程感知不到。我们可以统一捕获。
@Configuration public class AsyncConfig implements AsyncConfigurer { // 上面的线程池配置... @Override public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { return (ex, method, params) -> { log.error("异步方法异常:{}", method.getName()); log.error("异常信息:", ex); }; } }效果:
异步任务报错后,不会直接吞掉,会按你定义的方式打印/告警。
五、@Async 工作原理(面试常问)
@EnableAsync开启 AOP 代理。@Async方法被调用时,Spring 拦截并提交到线程池。- 由线程池里的线程执行目标方法。
- 主线程立即返回,不阻塞。
六、避坑指南(非常重要)
- 异步方法必须是 public,private 不生效。
- 不能同类内部调用,比如
this.asyncMethod(),不走代理。 - 返回值只能是 void / Future,用
ListenableFuture也可以。 - 生产必须自定义线程池,默认会无限创建线程。
- 异常必须单独处理,主线程 catch 不到。
- 事务不生效:异步方法和主线程不在一个事务里。
七、总结
Spring Boot 异步非常简单:
- 开启:
@EnableAsync - 标记:
@Async - 生产:自定义线程池 + 异常处理
- 场景:耗时、非核心、可并行任务
用好异步,能让你的接口性能直接上一个台阶。
降重鸟技术团队文章,勿全文转发
