线程池严格遵循“先用核心线程、再入队、队列满才扩容”策略;只有当workQueue.offer()返回false(即队列精确满)时,才创建新线程直至maximumPoolSize,否则绝不扩容。

线程池在达到最大线程数前,**不会因为任务提交快就立即扩容到 maximumPoolSize**,而是严格遵循:先用核心线程(corePoolSize),核心线程忙时进队列(workQueue),队列满后才创建新线程直至 maximumPoolSize。这个“队列必须先完全占满”是 ThreadPoolExecutor 的标准执行策略,可以用原生 Java 代码直观验证。
关键逻辑:拒绝策略触发点就是队列满的铁证
ThreadPoolExecutor 的 execute() 方法内部流程是固定的:
- 若当前线程数
- 否则尝试将任务加入 workQueue
- 若入队成功 → 结束;若失败(即 offer() 返回 false)→ 尝试新增线程(直到 maximumPoolSize)
- 若新增线程也失败(线程数已达 maximumPoolSize)→ 触发拒绝策略
也就是说,**只有当队列 offer() 明确返回 false 时,才会走“扩容线程”分支**。而 ArrayBlockingQueue、LinkedBlockingQueue 等常见队列的 offer() 正是在“已满”时才返回 false —— 这就是队列必须先被填满的直接证据。
用 ArrayBlockingQueue 写一个可观察的验证程序
选择有界队列(如 ArrayBlockingQueue),并设置较小容量,便于观察队列“从空到满再到触发扩容”的全过程:
import java.util.concurrent.*;
public class ThreadPoolQueueFullProof {
public static void main(String[] args) throws InterruptedException {
int core = 2, max = 4, queueCap = 3;
AtomicInteger queueSize = new AtomicInteger(0);
// 包装队列,监控入队动作
BlockingQueue<Runnable> queue = new ArrayBlockingQueue<Runnable>(queueCap) {
@Override
public boolean offer(Runnable e) {
boolean result = super.offer(e);
if (result) {
System.out.println("✅ 入队成功,当前队列大小: " + size());
queueSize.set(size());
} else {
System.out.println("❌ offer 失败!队列已满(size==" + queueCap + "),开始扩容线程...");
}
return result;
}
};
ThreadPoolExecutor pool = new ThreadPoolExecutor(
core, max, 60L, TimeUnit.SECONDS,
queue,
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝时在主线程执行,便于观察
);
// 提交 10 个慢任务(模拟阻塞)
for (int i = 0; i < 10; i++) {
final int taskId = i;
pool.submit(() -> {
System.out.println("?♂️ 任务 " + taskId + " 开始执行(线程:" + Thread.currentThread().getName() + ")");
try { Thread.sleep(3000); } catch (InterruptedException e) {}
System.out.println("? 任务 " + taskId + " 执行完成");
});
Thread.sleep(100); // 稍微错开提交时间,避免竞争干扰
}
pool.shutdown();
pool.awaitTermination(10, TimeUnit.SECONDS);
}
}
运行结果明确显示三阶段行为
你会看到类似输出:
- 前 2 个任务 → 启动 core 线程(pool-1-thread-1/2)
- 接下来 3 个任务 → 成功入队,打印 “✅ 入队成功,当前队列大小: 1/2/3”
- 第 6 个任务 → ❌ offer 失败!队列已满 → 此时 pool 创建第 3 个线程(thread-3)
- 第 7 个任务 → 再次 offer 失败 → 创建 thread-4(达到 max=4)
- 第 8~10 个任务 → offer 再失败 + 线程数已达 max → 触发拒绝策略(CallerRuns)
注意:第 6 个任务提交时,队列 size()==3(等于 capacity),offer() 返回 false —— 这不是“大概满了”,而是**精确的、由队列自身判定的满状态**,也是线程池决定扩容的唯一前提。
补充说明:为什么 LinkedBlockingQueue 容量为 Integer.MAX_VALUE 时看似不排队?
那是它的 capacity 实质上“几乎永不触发 offer 失败”,导致永远不扩容到 maximumPoolSize —— 这反而反向印证了原规则:只要队列 offer 不失败,线程池就绝不新建非核心线程。你可以把 LinkedBlockingQueue 的 capacity 改成 1 来复现相同行为,结论一致。

















