execute()按corePoolSize→队列offer→maximumPoolSize顺序调度任务;核心线程默认永驻,非核心线程空闲keepAliveSeconds后回收;须选有界队列防OOM;拒绝策略需显式配置以保障饱和时可控。

execute() 方法到底怎么调度任务?
调用 注意:这个顺序不可逆,也**不会因为队列里有任务就优先复用线程**——它只看“有没有核心线程名额”“队列能不能塞下”“还能不能加救急线程”。很多线上问题(如任务堆积却没扩容)都源于误以为“队列满才扩容”,其实是“队列满了 + 还没到最大线程数”才会扩容。 线程池不是“用完即焚”,而是靠两个独立机制控制生命周期:
- 常见误区:把 生产环境强烈建议用有界队列,并明确配置 当线程池彻底饱和(队列满 + 线程数达上限),拒绝策略决定系统如何“断尾求生”:
- 别用默认策略上线。至少选 立即学习“Java免费学习笔记(深入)”; 线程池不是配完参数就一劳永逸的东西,execute(Runnable task) 是任务进入线程池的第一步,但它的行为**不取决于“当前有多少空闲线程”,而取决于三个硬性条件的先后判断顺序**:
- 如果 poolSize → 立即新建核心线程执行,哪怕队列是空的;
- 否则如果队列 workQueue.offer(task) 成功 → 任务入队,等已有线程来取;
- 否则如果 poolSize → 新建非核心线程执行;
- 否则触发 RejectedExecutionHandler(比如抛 RejectedExecutionException)。
为什么新创建的线程不立刻销毁?
corePoolSize 内的线程默认永不超时(即使空闲),除非显式设置 allowCoreThreadTimeOut(true);
- 超出 corePoolSize 的线程,空闲超过 keepAliveSeconds 就会被回收。
keepAliveSeconds 当成“所有线程的存活时间”。其实它只对非核心线程生效。如果你设了 corePoolSize=5、maxPoolSize=20、keepAliveSeconds=60,那在低峰期你永远会留着 5 个线程,其余 15 个会在 60 秒空闲后消失。阻塞队列选错,等于埋雷
workQueue 不是随便传个 LinkedBlockingQueue 就完事。不同队列直接改变线程池行为逻辑:
- LinkedBlockingQueue(无界)→ 实际上让 maxPoolSize 失效(因为永远不会触发“队列满”条件),容易 OOM;
- ArrayBlockingQueue(有界)→ 配合 maxPoolSize 才能真正实现弹性扩容;
- SynchronousQueue(容量为 0)→ 每个任务必须立刻找到空闲线程,否则立即走拒绝策略,适合高吞吐低延迟场景。
queueCapacity(Spring 的 ThreadPoolTaskExecutor 中对应参数),避免因默认无界导致内存耗尽。拒绝策略不是摆设,得真能兜住
AbortPolicy(默认)→ 抛 RejectedExecutionException,上游若没 try-catch 就直接崩;
- CallerRunsPolicy → 让提交任务的线程自己执行该任务,可自然降速,但可能阻塞业务线程;
- DiscardOldestPolicy → 丢掉队列头任务,腾位置给新任务,适合“最新数据才重要”的场景;
- DiscardPolicy → 默默丢弃,零日志,最危险——问题发生时你根本不知道丢了啥。
CallerRunsPolicy 或自定义带日志的策略,确保饱和时行为可知、可追溯。corePoolSize、queueCapacity、maxPoolSize 三者必须按实际吞吐+延迟要求做组合压测,尤其要注意队列类型对扩容逻辑的隐性改写。










