1. 为什么需要异步任务?
在传统的同步处理模型中,当用户发起一个请求时,服务器会阻塞当前线程直到完成所有处理逻辑。这种模式在简单应用中表现良好,但当遇到以下场景时就会暴露出严重问题:
- 耗时操作(如文件导出、报表生成)会长时间占用线程资源
- 高并发场景下线程快速耗尽导致服务不可用
- 非核心业务(如日志记录、消息通知)阻塞主流程响应
Spring Boot通过@Async注解和TaskExecutor接口提供了开箱即用的异步处理能力。我曾在电商促销系统中实测,将订单履约后的短信通知改为异步后,接口响应时间从1200ms降至200ms,TPS提升了4倍。
重要提示:异步不是银弹,它适用于非核心且允许延迟处理的业务场景。对于需要强一致性的交易类操作,仍需谨慎评估。
2. 基础配置与核心注解
2.1 启用异步支持
在Spring Boot应用中启用异步只需要两个步骤:
- 主类添加@EnableAsync注解
java复制@SpringBootApplication
@EnableAsync
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
}
- 在需要异步执行的方法上添加@Async注解
java复制@Service
public class NotificationService {
@Async
public void sendSms(String phone, String content) {
// 模拟耗时操作
try {
Thread.sleep(1000);
log.info("短信发送成功:{}", phone);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
2.2 注解的隐藏细节
实际使用中我发现几个容易踩坑的特性:
- 自调用失效:同一个类内部方法调用@Async方法不会触发异步
java复制// 错误示例:不会异步执行
public void processOrder(Order order) {
this.sendSms(order.getPhone(), "订单创建成功");
}
// 正确做法:通过代理对象调用
@Autowired
private NotificationService selfProxy;
- 返回值处理:
- void方法:直接执行不关心结果
- 非void方法:必须返回Future或CompletableFuture
java复制@Async
public CompletableFuture<String> asyncQuery(String id) {
return CompletableFuture.completedFuture(queryFromDB(id));
}
- 异常处理:默认会吞掉异常,建议实现AsyncUncaughtExceptionHandler
java复制@Configuration
public class AsyncConfig implements AsyncConfigurer {
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> {
log.error("异步任务执行异常: {}", method.getName(), ex);
// 发送告警邮件等操作
};
}
}
3. 线程池的深度定制
3.1 默认线程池的隐患
Spring Boot默认使用SimpleAsyncTaskExecutor,这个实现有严重缺陷:
- 每次请求新建线程,没有复用机制
- 无限制创建可能导致资源耗尽
- 缺少队列和拒绝策略配置
在压力测试中,我观察到默认配置下1000并发请求会创建近800个线程,而合理配置的线程池只需50个线程即可处理。
3.2 推荐线程池配置
生产环境建议使用ThreadPoolTaskExecutor:
java复制@Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 核心线程数 = CPU核心数 * 2
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors() * 2);
// 最大线程数 = 核心线程数 * 3
executor.setMaxPoolSize(executor.getCorePoolSize() * 3);
// 队列容量 = 核心线程数 * 10
executor.setQueueCapacity(executor.getCorePoolSize() * 10);
// 线程名前缀
executor.setThreadNamePrefix("Async-Executor-");
// 拒绝策略:调用者线程执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 空闲线程存活时间(秒)
executor.setKeepAliveSeconds(60);
executor.initialize();
return executor;
}
关键参数说明:
- corePoolSize:常驻线程数,建议根据CPU密集型/IO密集型调整
- maxPoolSize:突发流量时的扩容上限
- queueCapacity:缓冲队列大小,需要权衡内存占用和吞吐量
- rejectedExecutionHandler:推荐策略:
- AbortPolicy(默认):直接抛出异常
- CallerRunsPolicy:主线程执行
- DiscardPolicy:静默丢弃
- DiscardOldestPolicy:丢弃最老任务
3.3 线程池的监控技巧
通过自定义ThreadPoolTaskExecutor的子类可以增加监控能力:
java复制public class MonitorableThreadPoolExecutor extends ThreadPoolTaskExecutor {
@Override
public void execute(Runnable task) {
super.execute(wrap(task));
}
private Runnable wrap(Runnable task) {
return () -> {
long start = System.currentTimeMillis();
try {
task.run();
} finally {
long cost = System.currentTimeMillis() - start;
if (cost > 1000) { // 记录慢任务
log.warn("异步任务执行耗时: {}ms", cost);
}
}
};
}
}
结合Spring Boot Actuator的Endpoint可以暴露线程池指标:
properties复制management.endpoints.web.exposure.include=health,metrics,threadpool
management.metrics.tags.application=${spring.application.name}
4. CompletableFuture的高级用法
4.1 基础组合操作
相比简单的@Async,CompletableFuture提供了更强大的流水线操作:
java复制@Async("asyncExecutor")
public CompletableFuture<String> getUserInfo(Long userId) {
return CompletableFuture.supplyAsync(() -> userService.getBasicInfo(userId))
.thenCombine(
CompletableFuture.supplyAsync(() -> creditService.getCreditScore(userId)),
(basic, credit) -> basic + "|" + credit
)
.exceptionally(ex -> {
log.error("获取用户信息异常", ex);
return "default";
});
}
4.2 超时控制技巧
原生CompletableFuture不支持超时,需要通过orTimeout方法扩展:
java复制public <T> CompletableFuture<T> withTimeout(CompletableFuture<T> future,
long timeout, TimeUnit unit) {
return future.orTimeout(timeout, unit)
.exceptionally(ex -> {
if (ex instanceof TimeoutException) {
log.warn("异步任务执行超时");
return null; // 或自定义默认值
}
throw new CompletionException(ex);
});
}
4.3 批量任务处理
使用allOf处理并行任务集合:
java复制List<CompletableFuture<String>> futures = productIds.stream()
.map(id -> CompletableFuture.supplyAsync(
() -> inventoryService.getStock(id),
asyncExecutor)
)
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList()))
.thenAccept(results -> {
// 处理所有结果
});
5. 生产环境避坑指南
5.1 事务失效问题
异步方法内调用@Transactional会失效,因为:
- Spring事务基于ThreadLocal实现
- 异步方法在新线程执行,无法获取原事务上下文
解决方案:
java复制@Transactional
public void syncProcess(Order order) {
// 同步方法内保存订单
orderRepository.save(order);
// 异步发送通知(事务提交后执行)
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
notificationService.sendSms(order.getPhone());
}
}
);
}
5.2 上下文传递问题
异步执行会丢失以下上下文:
- SecurityContext
- MDC日志追踪ID
- RequestAttributes
解决方案(以SecurityContext为例):
java复制@Async
public CompletableFuture<Void> asyncWithContext() {
SecurityContext context = SecurityContextHolder.getContext();
return CompletableFuture.runAsync(() -> {
SecurityContextHolder.setContext(context);
// 业务逻辑
}, asyncExecutor);
}
5.3 优雅关闭策略
应用关闭时需等待异步任务完成,避免数据不一致:
java复制@Bean
public TaskExecutorGracefulShutdown gracefulShutdown() {
return new TaskExecutorGracefulShutdown(asyncExecutor());
}
// 自定义关闭逻辑
public class TaskExecutorGracefulShutdown implements DisposableBean {
private final ThreadPoolTaskExecutor executor;
@Override
public void destroy() throws Exception {
executor.shutdown();
if (!executor.getThreadPoolExecutor().awaitTermination(30, TimeUnit.SECONDS)) {
log.warn("强制终止未完成的异步任务");
executor.getThreadPoolExecutor().shutdownNow();
}
}
}
6. 性能优化实战
6.1 线程池参数调优
通过压测工具(如JMeter)寻找最优参数组合:
- 固定并发数,逐步增加corePoolSize观察吞吐量变化
- 在吞吐量拐点附近调整queueCapacity
- 模拟突发流量测试maxPoolSize效果
实测案例(4核8G服务器):
| 场景 | core | max | queue | TPS | 平均耗时 |
|---|---|---|---|---|---|
| 默认配置 | 8 | 无 | 无 | 1200 | 450ms |
| 优化配置 | 16 | 32 | 200 | 5800 | 85ms |
| 队列过大 | 16 | 32 | 1000 | 5200 | 210ms |
6.2 异步编排模式
复杂业务流程的异步优化示例:
java复制public CompletableFuture<OrderResult> createOrder(OrderRequest request) {
// 并行校验
CompletableFuture<Void> stockCheck = CompletableFuture.runAsync(
() -> inventoryService.checkStock(request), asyncExecutor);
CompletableFuture<Void> riskCheck = CompletableFuture.runAsync(
() -> riskService.check(request), asyncExecutor);
return CompletableFuture.allOf(stockCheck, riskCheck)
.thenCompose(v -> {
// 串行创建订单
return CompletableFuture.supplyAsync(
() -> orderService.create(request), asyncExecutor);
})
.thenApply(order -> {
// 异步通知
notificationService.sendSms(order.getPhone());
return OrderResult.success(order);
});
}
6.3 监控与告警方案
推荐监控指标:
- 线程池活跃度 = activeCount / maximumPoolSize
- 任务队列饱和度 = queueSize / queueCapacity
- 任务平均耗时
- 拒绝任务计数
通过Prometheus + Grafana实现可视化:
java复制@Bean
public MeterBinder threadPoolMetrics(ThreadPoolTaskExecutor executor) {
return registry -> {
Gauge.builder("thread.pool.active",
() -> executor.getThreadPoolExecutor().getActiveCount())
.tag("name", executor.getThreadNamePrefix())
.register(registry);
Gauge.builder("thread.pool.queue.size",
() -> executor.getThreadPoolExecutor().getQueue().size())
.tag("name", executor.getThreadNamePrefix())
.register(registry);
};
}
