线程池饱和时三阶段流转为:核心线程→任务队列→最大线程;通过SynchronousQueue(容量0)、corePoolSize=1、maxPoolSize=2及3个2秒任务,可清晰观测Task-0在core线程、Task-1触发扩容、Task-2由main线程执行拒绝策略的全过程。

<p>用一段极简但有明确行为边界的代码,就能清晰观察线程池在饱和时的三阶段流转:核心线程 → 任务队列 → 最大线程(必要时)。</p>
<H3>关键设计:让三阶段依次触发</H3>
<p>不靠日志猜,而是用可验证的阻塞+计数方式,让每个阶段“显形”:</p>
<ul>
<li>用 <strong>SynchronousQueue</strong>(容量为0)——迫使新任务立刻尝试创建新线程,跳过排队,直触最大线程上限</li>
<li>设置 <strong>corePoolSize=1, maxPoolSize=2, keepAliveTime=1s</strong> —— 小而分明的边界</li>
<li>提交3个任务,每个任务 sleep(2s) —— 前两个会分别占住 core 和 max 线程;第三个因无空闲线程、又无法入队(SynchronousQueue 拒绝),触发拒绝策略</li>
</ul>
<H3>极简可运行代码(Java)</H3>
<p><font color="blue">复制即跑,输出直接对应三阶段:</font></p>
<pre><code>ExecutorService pool = new ThreadPoolExecutor(
1, 2, 1, TimeUnit.SECONDS,
new SynchronousQueue<>(), // 关键:不缓存,逼出线程扩容
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝时由主线程执行,可见可测
);
// 提交3个长任务
IntStream.range(0, 3).forEach(i -> pool.submit(() -> {
System.out.println("Task-" + i + " running on " + Thread.currentThread().getName());
try { Thread.sleep(2000); } catch (InterruptedException e) {}
}));
pool.shutdown();
pool.awaitTermination(5, TimeUnit.SECONDS);
</code></pre>
<H3>你将看到的明确流转证据</H3>
<ul>
<li><strong>Task-0</strong>:在 <code>pool-1-thread-1</code>(核心线程)执行 → 核心线程启动</li>
<li><strong>Task-1</strong>:在 <code>pool-1-thread-2</code>(非核心线程)执行 → 队列满(SynchronousQueue 拒绝入队),触发扩容至 max</li>
<li><strong>Task-2</strong>:由 <code>main</code> 线程执行(CallerRunsPolicy)→ 饱和,拒绝策略生效,证实队列与线程均已达极限</li>
</ul>
<H3>换种队列再对比(验证“队列”角色)</H3>
<p>把 <code>SynchronousQueue</code> 换成 <code>new LinkedBlockingQueue(1)</code>:</p>
<ul>
<li>Task-0 → core 线程</li>
<li>Task-1 → 入队(队列还有1空位)</li>
<li>Task-2 → 队列满 → 扩容出 thread-2 执行</li>
<li>Task-3 → 队列满 + 线程达 max → 拒绝 → main 执行</li>
</ul>
<p>队列从“不存在”到“容量1”,输出线程名变化,就是队列在流转中真实参与的铁证。</p>