先问一个问题:你的项目里,有没有类似这样的代码?
java
new Thread(() -> {
// 发送邮件、记录日志、调用外部接口
}).start();
new Thread() 的问题很明显:每次请求都创建新线程,用完就丢,线程数量无上限,分分钟把系统搞崩。
正确的做法是使用线程池。SpringBoot 内置了强大的异步支持,几行配置就能搞定。
一、快速上手
1.1 开启异步
在启动类或配置类上加一个注解:
java
@SpringBootApplication
@EnableAsync // 就这一行
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
1.2 写一个异步方法
java
@Service
@Slf4j
public class NotificationService {
@Async // 加了这个注解,方法就变成异步了
public void sendEmail(String to, String content) {
log.info("发送邮件到: {}", to);
// 耗时操作...
}
}
1.3 调用
java
@RestController
public class UserController {
@Autowired
private NotificationService notificationService;
@PostMapping("/register")
public String register(String username, String email) {
// 主流程立即返回,邮件在后台慢慢发
notificationService.sendEmail(email, "欢迎注册");
return "注册成功";
}
}
效果:调用方不会等待邮件发送完成,接口会立即返回。
二、三个必知的坑
| 坑 | 现象 | 解决方案 |
|---|---|---|
| 内部调用不生效 | 同一个类的方法调用 @Async 方法,还是同步执行 |
把异步方法放到单独的 Service 中 |
| private 不生效 | 异步方法写成 private | 必须是 public |
| 返回普通对象会失效 | 返回 User、String 等 | 只能返回 void 或 CompletableFuture |
三、自定义线程池(生产必做)
默认的线程池太简陋,生产环境一定要自己配置:
java
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5); // 核心线程数
executor.setMaxPoolSize(20); // 最大线程数
executor.setQueueCapacity(100); // 队列大小
executor.setThreadNamePrefix("async-"); // 线程名前缀
executor.setRejectedExecutionHandler(
new ThreadPoolExecutor.CallerRunsPolicy() // 满了就让调用方执行
);
executor.initialize();
return executor;
}
}
参数怎么设?
| 场景 | corePoolSize | maxPoolSize | queueCapacity |
|---|---|---|---|
| 轻量任务多 | 10-20 | 50-100 | 200-500 |
| 重量任务多 | 2-5 | 10-20 | 100-200 |
| IO 密集型 | CPU核数×2 | CPU核数×4 | 200左右 |
使用自定义线程池:
java
@Async("taskExecutor") // 指定线程池名称
public void sendEmail(String to, String content) {
// ...
}
四、处理异步任务异常
异步方法抛出的异常,调用方是抓不到的。需要自定义异常处理器:
java
@Configuration
public class AsyncExceptionConfig implements AsyncConfigurer {
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> {
log.error("异步方法执行失败: {}.{}",
method.getDeclaringClass().getName(),
method.getName(), ex);
// 可以在这里发送告警
};
}
}
五、有返回值的异步
如果需要拿到异步执行的结果:
java
@Async
public CompletableFuture<String> fetchData() {
// 模拟耗时
Thread.sleep(3000);
return CompletableFuture.completedFuture("结果数据");
}
// 调用方
CompletableFuture<String> future = dataService.fetchData();
String result = future.get(5, TimeUnit.SECONDS); // 等待最多5秒
总结
| 要点 | 一句话 |
|---|---|
| 开启 | @EnableAsync |
| 使用 | @Async |
| 线程池 | 必须自定义,别用默认的 |
| 异常 | 单独处理,别丢了 |
| 返回值 | void 或 CompletableFuture |
最后记住一句话:
异步不是银弹。IO 密集型、不需要返回结果的场景用它;CPU 密集型、需要事务的场景别乱用。