Spring 的 TaskExecutor 抽象任务提交。常见的 ThreadPoolTaskExecutor 是围绕
ThreadPoolExecutor 的受管 JavaBean 包装器;其线程池、队列、拒绝和生命周期选择仍决定系统容量。
定义和命名执行器 #
按照工作负载命名执行器,使调用方选择预期资源:
@Bean("reportExecutor")
ThreadPoolTaskExecutor reportExecutor() {
var executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(8);
executor.setMaxPoolSize(24);
executor.setQueueCapacity(100);
executor.setKeepAliveSeconds(30);
executor.setThreadNamePrefix("report-");
executor.setRejectedExecutionHandler(
new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}Spring 会将 ThreadPoolTaskExecutor 作为受管 bean 初始化和关闭。
返回其具体类型可以保留生命周期配置和监控方法。
应使用不同执行器隔离具有不同延迟、阻塞、优先级或下游容量要求的工作负载。
线程池、队列和饱和行为 #
底层执行器遵循明确的接纳顺序:
- 创建工作线程,直到达到
corePoolSize。 - 工作队列接受任务时,将新任务排队。
- 只有队列拒绝任务后,才创建线程,直到达到
maxPoolSize。 - 线程池和队列都饱和时,应用拒绝策略。
因此,无界队列会阻止线程数超过核心大小,并将过载转化为不断增长的延迟和内存使用。
有界队列能使过载可观察。AbortPolicy 以异常拒绝,CallerRunsPolicy 通过让提交线程运行任务施加背压,
而丢弃策略会丢失工作,需要明确的产品决策。
核心/最大大小、队列容量和保活时间必须依据测得的任务服务时间、阻塞比例、到达突发和下游限制。 更大的线程池无法增加数据库的连接容量。
@Async 选择和失败
#
使用 @EnableAsync 启用方法拦截。当应用有多个执行器时,选择一个命名执行器:
@Async("reportExecutor")
public CompletableFuture<Report> render(ReportRequest request) {
return CompletableFuture.completedFuture(renderer.render(request));
}没有注解限定符时,基础设施会查找唯一的 TaskExecutor,或查找名为 taskExecutor 的 Executor bean;
AsyncConfigurer 可以提供显式默认值。基于代理的 @Async 与其他 Spring AOP 通知具有相同的自调用限制。
Future 或 CompletableFuture 方法的失败通过其结果观察。
void 异步方法没有结果通道,因此应配置 AsyncUncaughtExceptionHandler;
单独记录日志不是交付或重试保证。
上下文传播 #
执行器任务在另一个线程上运行。线程本地 MDC、安全、区域设置和观测上下文不会自动传递。
TaskDecorator 可以在提交任务时捕获选定状态,在执行前后恢复状态,并在 finally 中清理。
只复制必需上下文,因为过时或秘密状态泄漏到复用的工作线程是正确性和安全缺陷。
优雅关闭 #
关闭策略必须匹配任务语义。配置执行器是否等待排队工作,并设置有限的等待终止时间。 服务关闭期间,先停止接受新请求,然后在截止时间内让已接受工作完成,最后取消或持久化未完成工作。 内存中的执行器队列不是持久存储。
Spring Boot 和虚拟线程 #
Spring Boot 3.2 及更高版本可以使用 Java 21 虚拟线程作为自动配置的异步执行器:
spring.threads.virtual.enabled=true虚拟线程降低每个阻塞任务占用一个线程的成本,但不会限制对数据库、HTTP 服务或其他稀缺资源的并发调用。 为此应使用信号量、连接池或工作负载接纳限制。 当 Boot 选择按任务创建虚拟线程的执行器时,许多线程池大小属性不适用。
在 JDK 21 到 23 中,持有监视器时阻塞,或在某些本地代码中阻塞,可能将虚拟线程固定到载体线程。 JDK 24 的 JEP 491 消除了基于监视器的固定,但本地和外部函数边界仍需测量。 使用 Java Flight Recorder 和线程转储诊断实际部署的 JDK 版本,不要假设虚拟线程让所有阻塞都无害。
评论
在 GitHub 上阅读或评论(发表评论需要登录)