corePoolSize是长期保留的最小线程数而非初始数;maximumPoolSize仅在队列满时生效,与队列类型强绑定;拒绝策略需配合有界队列才有效;allowCoreThreadTimeOut开启后核心线程也可超时回收。

corePoolSize设太小会导致线程频繁创建销毁
它不是“初始线程数”,而是“长期保留在池里的最小线程数量”。只要线程空闲时间没超keepAliveTime,哪怕没任务,也不会被回收。
常见错误是设成1,结果高并发时大量任务排队,新任务来了又得临时扩容——但扩容前得先走工作队列判断是否满,这一来一回反而拖慢响应。更糟的是,如果队列用的是LinkedBlockingQueue(默认无界),maximumPoolSize干脆没机会触发,所有压力都堆在队列里,内存涨得飞快。
- IO密集型场景(如HTTP调用、DB查询)建议设为
2 * CPU核心数起步,留出等待间隙 - CPU密集型任务别超过
CPU核心数 + 1,否则上下文切换开销反超收益 - 设为0是合法的,但意味着“不预热”,第一个任务进来就得走完整扩容流程
maximumPoolSize只在工作队列满时才生效
很多人以为线程池会“先填满队列再扩容”,其实逻辑是:新任务提交 → 尝试加进队列 → 队列加不进(满或拒绝)→ 再尝试创建新线程到maximumPoolSize上限。
所以它和队列类型强绑定。比如用ArrayBlockingQueue(有界),队列满得快,maximumPoolSize容易触达;换成SynchronousQueue(容量为0),每个任务都直接尝试创建线程,maximumPoolSize就成了实际最大并发数。
立即学习“Java免费学习笔记(深入)”;
-
SynchronousQueue配大maximumPoolSize= 高并发容忍度,但OOM风险也高 -
LinkedBlockingQueue配大maximumPoolSize= 基本无效,除非你重写了offer()让它返回false - 别把
maximumPoolSize设成Integer.MAX_VALUE,这是在赌JVM不会因线程过多崩溃
工作队列选错会让拒绝策略形同虚设
拒绝策略(RejectedExecutionHandler)只在“队列加不进 + 线程数已达maximumPoolSize”时触发。如果队列本身不拒绝(比如无界队列),那永远等不到拒绝那一刻。
典型现象:服务看着CPU不高、线程数稳定,但接口RT飙升、监控里堆积任务数持续上涨——其实是LinkedBlockingQueue默默吞掉了几千个任务,等着某个线程慢慢消费。
- 想让拒绝策略真正起作用,队列必须是“可能满”的,推荐
ArrayBlockingQueue或SynchronousQueue -
PriorityBlockingQueue虽能排序,但它不实现size()精确计数,且offer()永不返回false,拒绝策略同样失效 - 自定义队列要重写
offer(E e),确保容量不足时返回false,否则ThreadPoolExecutor识别不了“队列已满”
allowCoreThreadTimeOut开启后corePoolSize行为彻底改变
默认情况下,核心线程永不超时;但设了allowCoreThreadTimeOut(true)之后,连核心线程也会在空闲keepAliveTime后被回收——此时corePoolSize就真成了“动态下限”,而不是“静态保留数”。
这在流量波动大的后台任务调度中很有用,但要注意:如果keepAliveTime太短(比如100ms),线程刚建好就销毁,又立刻重建,白白消耗资源。
- 仅当明确需要“弹性收缩”时才开,Web服务器类长连接场景通常关着更稳
- 开了之后,
corePoolSize == 0是可行的,等价于“全按需创建,无常驻线程” - 别忘了同步调整
keepAliveTime,30秒是较安全的起点,比默认的60秒更早释放,又不至于太激进
事情说清了就结束。最常被忽略的是:队列类型和拒绝策略之间存在隐式契约,而corePoolSize和allowCoreThreadTimeOut的组合效果,远比文档里写的更微妙。










