0. 先明确 3 个线程池常量
这些是底层硬编码规则,所有逻辑都基于此:
最小工作线程数(MinThreads)
- 默认 = CPU 核心数
- 线程数 ≤ 最小值:立即创建、永不回收
最大工作线程数(MaxThreads)
- 默认 = 32767(很大,基本不会触达)
- 达到上限后,不再创建新线程,任务排队
两个关键时间
- 扩容节流时间:超过最小线程后,每 500ms 最多创建 1 个新线程
- 空闲回收时间:超过最小线程的线程,空闲 1 秒后自动销毁
1. 线程池工作线程:扩容逻辑
理论原理
线程池不会瞬间创建大量线程,而是非常保守地扩容,防止 CPU 爆炸。
扩容流程:
- 任务进来 → 检查是否有空闲线程
- 无空闲 → 检查当前线程数 < 最小线程数?
- 是:立即创建新线程(0 等待)
- 否:进入 500ms 限流创建
- 每 500ms 只允许新增1 个线程
- 直到达到最大线程数,停止扩容
代码演示:触发线程池扩容
using System; using System.Threading; using System.Threading.Tasks; class ThreadPoolDemo { static void Main() { // 一、手动设置线程池,方便观察:最小 3 个,最大 10 个 ThreadPool.SetMinThreads(3, 3); ThreadPool.SetMaxThreads(10, 10); Console.WriteLine("=== 开始提交 10 个长时间任务 ==="); // 二、一次性扔 10 个任务,触发扩容 for (int i = 0; i < 10; i++) { int num = i; ThreadPool.QueueUserWorkItem(Worker, num); } // 三、后台监控线程池状态(1 秒打印一次) Task.Run(Monitor); Console.ReadLine(); } // 长时间工作任务:让线程一直占用,不释放 static void Worker(object state) { int id = (int)state; Console.WriteLine($"[任务 {id}] 运行中 | 线程ID: {Thread.CurrentThread.ManagedThreadId}"); // 模拟耗时 10 秒,让线程一直忙,迫使线程池不断扩容 Thread.Sleep(10000); } // 监控:打印繁忙线程数、空闲线程数 static async Task Monitor() { while (true) { ThreadPool.GetMaxThreads(out int maxWorker, out _); ThreadPool.GetAvailableThreads(out int availWorker, out _); int busyWorker = maxWorker - availWorker; Console.WriteLine($"\n[监控] 繁忙工作线程:{busyWorker} | 可用空闲:{availWorker}"); await Task.Delay(1000); } } }运行结果分析(扩容现象)
你会看到控制台输出:
- 前3 个任务立即执行(因为最小线程 = 3)
- 第 4 个任务不会马上运行,要等 500ms才会新建线程运行
- 第 5、6… 个任务每隔 500ms 才会运行一个
- 线程数缓慢上涨,直到 10 个(最大线程)
这就是500ms 扩容限流。
2. 线程池工作线程:空闲逻辑
当线程执行完任务,不会立刻销毁,而是进入空闲等待状态:
- 线程完成任务
- 去线程池队列尝试获取下一个任务
- 没有任务 → 进入空闲等待
- 空闲时:
- 不占用 CPU
- 处于内核等待状态
- 随时可以被新任务唤醒
- 线程依然存活,只是休眠
观察空闲状态
static void Main() { ThreadPool.SetMinThreads(2, 2); Console.WriteLine("提交 1 个短任务"); ThreadPool.QueueUserWorkItem((_) => { Console.WriteLine("任务执行完成"); // 执行完后,线程不会退出,进入空闲等待 }); Console.ReadLine(); }现象
任务执行完后,线程不会消失,它回到线程池,处于空闲休眠状态。
3. 线程池工作线程:回收逻辑
线程池只回收多余的线程,最小线程数以内的永久保活。
回收规则:
- 线程处于空闲等待
- 空闲时间 ≥1 秒
- 当前总线程数> 最小线程数→满足:线程被销毁、回收、释放内存
- 当前总线程数≤ 最小线程数→不回收,永久保活
最小以内永久留,超出部分闲 1 秒就杀。线程回收
static void Main() { // 最小 2 个线程 ThreadPool.SetMinThreads(2, 2); ThreadPool.SetMaxThreads(10, 2); Console.WriteLine("=== 提交 5 个任务,触发扩容 ==="); for (int i = 0; i < 5; i++) { ThreadPool.QueueUserWorkItem((_) => { Console.WriteLine($"任务运行 | 线程ID:{Thread.CurrentThread.ManagedThreadId}"); Thread.Sleep(2000); // 运行 2 秒 Console.WriteLine("任务结束"); }); } Task.Run(Monitor); Console.ReadLine(); }运行结果分析(回收现象)
- 运行时,繁忙线程数涨到5
- 2 秒后所有任务结束
- 等待1 秒
- 你会看到监控:繁忙线程从 5 → 慢慢降到 2(超过最小线程的 3 个线程被回收销毁)
4. 流程图
任务来了
新任务 → 有空闲线程?→ 是:直接执行 ↓ 否 当前线程数 < 最小线程数?→ 是:立即新建线程 ↓ 否 进入 500ms 等待 → 创建 1 个线程 重复直到达到最大线程数任务执行完
任务结束 → 线程回到线程池 → 等待新任务 ↓ 等待超时(1秒) 当前线程数 > 最小线程数?→ 是:销毁线程(回收) ↓ 否 继续等待(永久保活)5. 总结
扩容小于最小线程:立即创建大于最小线程:每 500ms 创建 1 个
空闲线程执行完任务,不退出,休眠等待新任务,不占 CPU
回收大于最小线程的部分:空闲 1 秒 → 销毁小于等于最小线程:永久不回收