qinghuan
← Java 执行与测试边界目录

JAVA EXECUTION / 补充讲义 1 / 3

异步工具线程池的排队、扩容与饱和拒绝

用门闩分别观察入队、扩容、拒绝与 CallerRunsPolicy 在提交线程执行任务的边界。

异步工具提交成功以后,任务可能正在运行,也可能还在排队。先固定执行器容量和拒绝策略,再观察任务何时、在哪条线程上开始。

前置:工具调用与取消。适用于商户查询工具已跑通、开始配置自定义异步执行器的阶段。以下代码与门闩实验均未运行。

排队次序与提交线程

ThreadPoolTaskExecutor 的核心线程数、最大线程数和队列容量必须一起看。达到 corePoolSize 后先入队,队列满后才扩至 maxPoolSize;线程和队列都满时拒绝任务。因此无界队列不会因为 maxPoolSize 较大就自动扩容到最大值,并可能积累大量待处理工作。API 的默认配置是核心线程 1、最大线程和队列容量无限;这是该类的默认值,不等于 Spring Boot 自动配置或用户当前项目的值。

默认 AbortPolicy 对应 Spring 的 TaskRejectedException;CallerRunsPolicy 在执行器仍运行的饱和场景下让提交线程自己执行任务。工程含义是“没有抛拒绝异常”不等于“已异步交给后台”,调用线程可能被慢查询占住。不要以静默丢弃来伪装工具完成;排队限额也不是模型 token 预算、跨实例并发限额或持久化任务保证。

最小配置

以下数字只构造可控测试条件,不是生产参数建议。

文中示例 · java
var executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(1);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(1);
executor.setThreadNamePrefix("shop-test-");
executor.initialize();
// 使用 execute 提交测试任务;测试 finally 中释放门闩并 executor.shutdown()

练习一 观察拒绝与扩容

用 CountDownLatch 控制两个虚构慢查询,不依赖 sleep 猜时序。任务 A 启动后阻塞;提交 B 留在队列;提交 C 后等待 C 已启动并阻塞;此时提交 D,断言抛 TaskRejectedException 且 D 的业务计数为零。记录 A/C 已开始、B 未开始及各任务终态;finally 释放门闩。再用新执行器设置足够大的队列作有限对照,验证 B/C 入队而不是因为 max=2 立即产生第二个工作线程。不要实际制造内存耗尽。

练习二 观察 CallerRunsPolicy

新建同规格执行器并显式设置 new ThreadPoolExecutor.CallerRunsPolicy(),在 A/C 阻塞、B 排队后,由独立受控提交线程调用 execute(D)。让 D 记录线程名并通过另一门闩阻塞;断言 D 在该提交线程执行,execute 尚未返回,释放 D 后才返回。测试必须设置有界等待与 finally 清理,避免测试自身挂死;只覆盖运行中的饱和,不推论关闭后的行为。

验收范围

已有异步身份、日志上下文和工具预算的规则仍需分别验收。这里没有核验 Spring Boot 实际执行器配置、数据库连接池联动或生产容量;按项目依赖版本核对后再执行,不要求为此升级。

来源与版本

Spring Framework 7.0.10。资料原核查时间为 2026 年 10 月 27 日 00:12–00:13 JST;独立发布日期未标明。来源:Task Execution and Scheduling 的 executor 配置章节、ThreadPoolTaskExecutor API。

固定版本实现对照