corePoolSize和maximumPoolSize在ThreadPoolExecutor初始化时定死,不可动态修改;传参顺序为构造函数前两个参数,corePoolSize=maximumPoolSize等效固定线程池,corePoolSize=0会导致任务积压队列,需避免。

ThreadPoolExecutor 初始化时怎么设置 corePoolSize 和 maximumPoolSize
核心线程数 corePoolSize 和最大线程数 maximumPoolSize 是初始化时就定死的,不能动态改。传参顺序别搞反——ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue,前两个参数就是它们。
常见误用是把 corePoolSize 设为 0:这会导致刚提交任务时没线程可用,除非队列满了或调用了 prestartAllCoreThreads(),否则任务会卡在队列里等超时或被拒绝。
-
corePoolSize = 1+maximumPoolSize = 4:适合短时突发负载,空闲线程保底 1 个,最多扩到 4 个 -
corePoolSize == maximumPoolSize:等效于固定大小线程池,避免扩容开销,但失去弹性 - 设得过大(比如 > CPU 核心数 × 2)容易引发上下文切换抖动,尤其 I/O 密集型任务才可适度放宽
如何指定自定义拒绝策略(RejectedExecutionHandler)
默认的 AbortPolicy 遇到饱和直接抛 RejectedExecutionException,生产环境通常不能接受。换策略必须显式传入第 5 个构造参数:RejectedExecutionHandler handler。
JDK 自带四种策略,行为差异很大:
-
CallerRunsPolicy:由提交任务的线程自己执行该任务——适合能容忍延迟、且调用线程非关键路径的场景 -
DiscardPolicy:静默丢弃,不报错也不通知,容易掩盖问题 -
DiscardOldestPolicy:丢掉队列头任务,再尝试提交当前任务——注意不是丢正在执行的,是丢排队最久的 - 自定义实现
RejectedExecutionHandler接口:比如打日志 + 上报监控 + 落库重试
示例自定义策略:
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
log.warn("Task {} rejected, pool size: {}, queue size: {}",
r, executor.getPoolSize(), executor.getQueue().size());
// 可选:发告警、存 DB、走降级逻辑
}
}
workQueue 类型对拒绝时机的影响
拒绝不是只看线程数,关键是「线程全忙 + 队列已满」。所以队列类型直接决定什么时候触发拒绝策略。
-
LinkedBlockingQueue(无界):默认容量Integer.MAX_VALUE,几乎不会满 →maximumPoolSize形同虚设,拒绝策略永不触发 -
ArrayBlockingQueue(有界):容量固定,队列满 + 线程达maximumPoolSize时立即触发拒绝 -
SynchronousQueue(无缓冲):不存任务,每个任务必须立刻有空闲线程接手,否则立刻拒绝——适合高响应要求场景
别用无界队列配小 corePoolSize,内存可能被撑爆;也别用 SynchronousQueue 配大 maximumPoolSize,线程创建成本高,容易 OOM。
为什么 setCorePoolSize() 不会影响已运行的线程数
setCorePoolSize(int) 只影响后续的线程生命周期管理:如果新值比当前值小,已有线程不会被主动回收,要等空闲超时后自然退出;如果新值更大,也不会立刻创建新线程,而是等新任务来且当前线程数
也就是说,它不“即时生效”,只是调整了线程池的“扩缩容阈值”。真正想立刻拉起线程,得调 prestartCoreThread() 或 prestartAllCoreThreads()。
典型误区是以为调了 setCorePoolSize(8) 就马上有 8 个线程在跑——其实没有,除非你手动触发预热,或者等任务进来慢慢拉起。










