前言
说起@Async
注解,很多Java开发者都觉得这是个神器------只要在方法上加个注解,瞬间就能实现异步执行,简直不要太爽!
但是,你真的了解@Async
背后的机制吗?
今天我就来给你盘明白@Async
注解的那些坑,让你彻底搞懂它的工作原理,避免在生产环境中踩到雷。

正文
@Async的六大常见坑
1. 默认线程池
问题描述
很多人以为Spring会智能地管理线程池,其实并非如此。实际上默认情况下,Spring使用的是SimpleAsyncTaskExecutor
。
这个执行器有个大坑:它根本不是线程池!
- 每次调用异步方法都创建新线程
- 没有线程数量限制
- 任务完成后线程立即销毁
如果系统里同时产生1000个异步任务,那就会创建1000个线程。在高并发场景下,恐怕就得炸锅了。
举个 🌰 :
java
@Service
public class UserService {
@Async
public void sendEmail(String email) {
// 发送邮件逻辑
System.out.println("发送邮件给: " + email);
}
}
当你批量调用这个方法时:
java
// 这样做会创建1000个线程
for (int i = 0; i < 1000; i++) {
userService.sendEmail("user" + i + "@example.com");
}
结果就是:
- 系统瞬间创建1000个线程
- 内存占用急剧上升
- CPU上下文切换频繁
- 最严重的是可能导致OOM
解决方案
一般会用自定义线程池来解决这个问题:
java
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("taskExecutor")
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10); // 核心线程数
executor.setMaxPoolSize(50); // 最大线程数
executor.setQueueCapacity(200); // 队列容量
executor.setThreadNamePrefix("async-task-");
executor.initialize();
return executor;
}
}
然后在使用时指定线程池:
java
@Service
public class UserService {
@Async("taskExecutor") // 指定使用自定义线程池
public void sendEmail(String email) {
System.out.println("发送邮件给: " + email);
}
}
有些人不知道线程池如何设置参数,这里有个万能公式:
- CPU密集型任务:核心线程数 = CPU核心数 + 1
- IO密集型任务:核心线程数 = CPU核心数 × 2
- 队列容量:一般设置为核心线程数的5-10倍
2. 异常处理
问题描述
这是一个比较隐蔽的坑,不少人会忽略掉:异步方法中的异常不会被调用方捕获。
java
@Async
public void riskyMethod() {
throw new RuntimeException("我是异常"); // 这个异常会被吞掉!
}
即便用try-catch包裹起来调用:
java
try {
service.riskyMethod();
} catch (Exception e) {
// 仍然捕获不到异常
}
这就导致了:
- 异常被静默吞掉
- 问题难以排查
- 严重时导致业务状态不一致
解决方案
方法一:全局异常处理器
java
@Configuration
public class AsyncConfig implements AsyncConfigurer {
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> {
System.err.println("异步方法异常: " + method.getName() + " - " + ex.getMessage());
// 可以发邮件、记录日志等
};
}
}
方法二:返回Future
这个再下面也会再讲到。
java
@Async
public CompletableFuture<String> safeAsyncMethod() {
try {
// 业务逻辑
return CompletableFuture.completedFuture("成功");
} catch (Exception e) {
CompletableFuture<String> future = new CompletableFuture<>();
future.completeExceptionally(e);
return future;
}
}
这样调用方就能捕获异常了:
java
try {
CompletableFuture<String> future = service.safeAsyncMethod();
String result = future.get(3, TimeUnit.SECONDS);
} catch (ExecutionException e) {
System.out.println("捕获到异常: " + e.getCause().getMessage());
}
3. 内部方法调用无效
问题描述
这其实是Spring AOP的经典问题了,和事务类似:在同一个类内部调用标注了@Async
的方法,异步注解不会生效。
为什么会这样?因为Spring的AOP是基于代理模式实现的:
- Spring会为标注了
@Async
的类创建代理对象 - 只有通过代理对象调用方法,异步才会生效
- 类内部的
this.method()
调用,绕过了代理,所以异步失效
java
@Service
public class UserService {
public void publicMethod() {
this.asyncMethod(); // 这样调用异步无效!直接调用原对象方法
}
@Async
public void asyncMethod() {
System.out.println("我应该异步执行,但实际上是同步的...");
// 不会异步执行
}
}
解决方案
方法一:拆分成不同的Service类
java
@Service
public class UserService {
@Autowired
private AsyncService asyncService;
public void publicMethod() {
asyncService.asyncMethod(); // 通过Spring容器注入的代理对象调用
}
}
@Service
public class AsyncService {
@Async
public void asyncMethod() {
System.out.println("现在可以异步执行了!");
}
}
方法二:使用ApplicationContext获取代理对象
java
@Service
public class UserService implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void publicMethod() {
UserService proxy = applicationContext.getBean(UserService.class);
proxy.asyncMethod(); // 通过代理对象调用
}
@Async
public void asyncMethod() {
System.out.println("通过代理调用,异步生效!");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
}
方法三:使用AopContext(需要开启expose-proxy)
java
// 在配置类中开启
@EnableAspectJAutoProxy(exposeProxy = true)
@Service
public class UserService {
public void publicMethod() {
UserService proxy = (UserService) AopContext.currentProxy();
proxy.asyncMethod(); // 通过代理对象调用
}
@Async
public void asyncMethod() {
System.out.println("通过AopContext获取代理,异步生效!");
}
}
4. 方法必须是public
问题描述
@Async
注解只对public方法有效,private和protected方法都无效。
还是因为Spring AOP基于代理模式,而代理只能拦截public方法的调用。
java
@Service
public class UserService {
@Async // 无效!private方法不会被代理
private void privateAsyncMethod() {
System.out.println("我不会异步执行");
}
@Async // 无效!protected方法不会被代理
protected void protectedAsyncMethod() {
System.out.println("我也不会异步执行");
}
@Async // 有效!public方法可以被代理
public void publicAsyncMethod() {
System.out.println("我会异步执行");
}
}
为什么Spring AOP有这个限制?
- JDK动态代理:只能代理接口,所有方法都是public
- CGLIB代理:可以代理类,但private方法无法被重写,因此无法被代理
解决方案
很简单,将需要异步执行的方法改为public:
java
@Service
public class UserService {
// 如果原来是private,改为public
@Async
public void asyncMethod() {
System.out.println("现在可以异步执行了!");
}
// 如果担心误调用,可以通过包结构来控制访问权限
// 或者将异步方法抽取到专门的Service中
}
更好的做法是将异步方法抽取到专门的Service中:
java
@Service
public class AsyncTaskService {
@Async
public void executeAsyncTask() {
// 专门处理异步任务的Service
System.out.println("异步任务执行中...");
}
}
@Service
public class UserService {
@Autowired
private AsyncTaskService asyncTaskService;
public void businessMethod() {
// 业务逻辑
asyncTaskService.executeAsyncTask(); // 调用异步服务
}
}
5. 忘记开启异步支持
问题描述
其实不少人踩过这个坑,写的时候忘记在配置类上加@EnableAsync
注解,或者忘记检查有没有加过@EnableAsync
注解,导致异步注解完全不生效。
没有这个注解,Spring根本不会处理@Async
注解。
java
// 错误示例 - 缺少@EnableAsync注解
@Configuration
public class AsyncConfig {
@Bean("taskExecutor")
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.initialize();
return executor;
}
}
@Service
public class UserService {
@Async // 这个注解不会生效,因为没有开启异步支持
public void asyncMethod() {
System.out.println("我实际上是同步执行的...");
}
}
测试一下就能发现问题:
java
@SpringBootTest
public class AsyncTest {
@Autowired
private UserService userService;
@Test
public void testAsync() {
System.out.println("主线程: " + Thread.currentThread().getName());
userService.asyncMethod();
System.out.println("方法调用结束");
// 会发现asyncMethod和主线程是同一个线程
}
}
解决方案
在配置类上添加@EnableAsync
注解:
java
// 正确示例
@Configuration
@EnableAsync // 开启异步支持
public class AsyncConfig {
@Bean("taskExecutor")
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(200);
executor.setThreadNamePrefix("async-task-");
executor.initialize();
return executor;
}
}
或者在SpringBoot主类上添加:
java
@SpringBootApplication
@EnableAsync // 也可以在主类上开启
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
建议在Configuration类上加,不要一股脑全部加到启动类上。
6. 返回值类型限制
问题描述
如果异步方法有返回值,只能返回Future
类型或其子类(如CompletableFuture
),否则方法不会异步执行,而是会同步执行并返回结果。
java
@Service
public class UserService {
@Async // 这个方法不会异步执行!
public String wrongReturnType() {
System.out.println("我应该异步执行,但实际上是同步的: " + Thread.currentThread().getName());
try {
Thread.sleep(2000); // 模拟耗时操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "我是同步返回的结果";
}
@Async // void返回类型,可以异步执行
public void voidAsyncMethod() {
System.out.println("我会异步执行: " + Thread.currentThread().getName());
}
}
解决方案
如果需要返回值,使用Future类型:
java
@Service
public class UserService {
@Async
public CompletableFuture<String> correctReturnType() {
System.out.println("我会异步执行: " + Thread.currentThread().getName());
try {
Thread.sleep(2000); // 模拟耗时操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return CompletableFuture.completedFuture("执行被中断");
}
return CompletableFuture.completedFuture("我是异步返回的结果");
}
@Async
public Future<Integer> calculateAsync(int n) {
System.out.println("开始异步计算: " + Thread.currentThread().getName());
// 模拟复杂计算
int result = 0;
for (int i = 0; i < n; i++) {
result += i;
}
return new AsyncResult<>(result); // Spring提供的Future实现
}
@Async
public CompletableFuture<User> getUserAsync(Long userId) {
System.out.println("异步查询用户: " + Thread.currentThread().getName());
try {
// 模拟数据库查询
Thread.sleep(1000);
User user = new User(userId, "张三");
return CompletableFuture.completedFuture(user);
} catch (Exception e) {
// 异常处理
CompletableFuture<User> future = new CompletableFuture<>();
future.completeExceptionally(e);
return future;
}
}
}
调用带返回值的异步方法:
java
@Test
public void testCorrectReturnType() throws Exception {
System.out.println("主线程: " + Thread.currentThread().getName());
// 调用异步方法,立即返回Future对象
CompletableFuture<String> future = userService.correctReturnType();
System.out.println("主线程继续执行其他任务...");
// 可以设置超时时间,避免无限等待
String result = future.get(3, TimeUnit.SECONDS);
System.out.println("得到结果: " + result);
}
@Test
public void testMultipleAsync() throws Exception {
System.out.println("主线程: " + Thread.currentThread().getName());
// 同时启动多个异步任务
CompletableFuture<String> task1 = userService.correctReturnType();
CompletableFuture<Integer> task2 = userService.calculateAsync(1000);
CompletableFuture<User> task3 = userService.getUserAsync(123L);
// 等待所有任务完成
CompletableFuture.allOf(task1, task2, task3).get();
System.out.println("任务1结果: " + task1.get());
System.out.println("任务2结果: " + task2.get());
System.out.println("任务3结果: " + task3.get().getName());
}
处理异步方法的异常:
java
@Test
public void testAsyncException() {
CompletableFuture<User> future = userService.getUserAsync(999L);
future.whenComplete((user, throwable) -> {
if (throwable != null) {
System.out.println("异步方法发生异常: " + throwable.getMessage());
} else {
System.out.println("异步方法执行成功: " + user.getName());
}
});
}
更好的实践
不敢妄称最佳实践,但确实是多年开发下来比较好的习惯。
业务线程池隔离
不同业务使用不同的线程池,避免相互影响:
java
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("emailExecutor")
public TaskExecutor emailExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("email-task-");
executor.initialize();
return executor;
}
@Bean("smsExecutor")
public TaskExecutor smsExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(50);
executor.setThreadNamePrefix("sms-task-");
executor.initialize();
return executor;
}
}
监控线程池状态
定期检查线程池的运行状态:
java
@Component
public class ThreadPoolMonitor {
@Autowired
@Qualifier("emailExecutor")
private TaskExecutor emailExecutor;
@Scheduled(fixedRate = 30000)
public void monitorThreadPool() {
ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) emailExecutor;
System.out.println("邮件线程池状态:");
System.out.println("活跃线程数: " + executor.getActiveCount());
System.out.println("队列任务数: " + executor.getThreadPoolExecutor().getQueue().size());
System.out.println("已完成任务数: " + executor.getThreadPoolExecutor().getCompletedTaskCount());
}
}
当然也可以使用 dromara/dynamic-tp 做动态配置和监控,功能更加完备。
设置合理的拒绝策略
当线程池满了怎么办?设置合理的拒绝策略:
java
@Bean("taskExecutor")
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(200);
// 设置拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// CallerRunsPolicy:让调用线程执行任务(推荐)
// DiscardPolicy:直接丢弃任务
// DiscardOldestPolicy:丢弃最老的任务
// AbortPolicy:抛出异常(默认)
executor.initialize();
return executor;
}
设置超时时间
对于有返回值的异步方法,一定要设置超时时间,避免无限等待:
java
CompletableFuture<String> future = service.asyncMethod();
try {
String result = future.get(5, TimeUnit.SECONDS); // 最多等5秒
} catch (TimeoutException e) {
System.out.println("异步方法执行超时");
future.cancel(true); // 取消任务
}
优雅关闭
配置线程池优雅关闭,避免任务丢失:
java
@Bean("taskExecutor")
public TaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(200);
// 优雅关闭配置
executor.setWaitForTasksToCompleteOnShutdown(true); // 等待任务完成
executor.setAwaitTerminationSeconds(60); // 最多等待60秒
executor.initialize();
return executor;
}
结尾
@Async
用好了能提升性能,用错了就是定时炸弹,一旦并发量上来直接触发引线。
希望今天的分享能帮你避开这些坑,让你的异步编程更加稳定可靠。
遇到问题不要慌,理解原理最重要!
