corePoolSize=0并非不启动线程,而是触发“按需最小化启动”机制:任务入队后execute()二次检查workerCount为0时必启一个firstTask为null的非核心线程去队列取任务。

把 corePoolSize 设为 0,不是“不启动线程”,而是切换成一种「按需最小化启动」的模式——它把线程创建完全交给队列状态和运行时检查来驱动。掌握这个逻辑,等于摸清了线程池最底层的触发开关。
看懂 execute() 中那个关键的二次检查
很多资料说“corePoolSize=0 就直接入队、不建线程”,这在 JDK 1.6+ 是错的。真实流程中,任务成功入队后,execute() 会立刻再检查一次:当前 workerCount 是否为 0。如果是,就调用 addWorker(null, false) 启动第一个非核心线程。
- 这个线程的
firstTask是null,意味着它不直接执行刚提交的任务,而是启动后立即去队列take()拿任务 - 哪怕你只 submit 一个任务,只要线程池初始为空,就会拉起一个线程来消费它
- 这个行为与队列是否有界无关,是 ThreadPoolExecutor 的强制保障机制
区分队列类型,决定线程是否“真正沉睡”
corePoolSize=0 的表现高度依赖 workQueue 的 offer 行为,本质是看「入队是否总能成功」:
- LinkedBlockingQueue(无界):offer 几乎永远成功 → 首个任务入队 + 触发隐式启动 → 立即有线程开始消费 → 不会积压
- ArrayBlockingQueue(有界):队列满时 offer 失败 → 走第三分支,尝试 addWorker(command, false) → 按 maximumPoolSize 扩容
- SynchronousQueue:offer 总是失败(它不存储元素)→ 完全跳过队列,直接走扩容逻辑 → 每个任务都新建线程(直到达 maximumPoolSize)
验证空闲线程回收是否生效
corePoolSize=0 时,所有线程都是非核心线程,keepAliveTime 生效且不可绕过:
- 线程执行完任务后进入空闲,等待新任务;若超时未等到,就被回收
- 回收后 workerCount 变为 0,下一次新任务到来又会触发「入队 + 检查为 0 → 启动新线程」的完整链路
- 这意味着:低频任务场景下,线程池始终维持 0 个活跃线程,资源占用趋近于零
实战调试建议:三步定位启动时机
想亲眼看到线程怎么被拉起来?不用猜,靠日志和断点:
- 在
addWorker()方法入口加断点或日志,观察参数core是 true 还是 false,确认是否为非核心线程 - 在
runWorker(Worker w)中打印线程名和首次 take 时间,验证它是否真的从队列取任务而非执行 firstTask - 用 JConsole 或 VisualVM 监控
PoolSize和ActiveCount,对比任务提交节奏与线程生命周期

















