corePoolSize 是“常驻线程阈值”,新任务到来时若线程数未达该值即新建线程执行,无论线程是否空闲;workQueue 是“线程扩容开关”,仅当其满且线程数小于 maxPoolSize 时才创建非核心线程。

corePoolSize 不是“最小线程数”,而是“常驻线程阈值”
很多开发者误以为 corePoolSize 是线程池“至少要维持的线程数”,其实它更准确的含义是:**新任务到来时,只要当前线程数 corePoolSize,就立刻新建线程执行——哪怕此时所有线程都空闲着**。也就是说,它控制的是“是否允许线程池‘懒启动’”。
常见错误现象:
• 设置 corePoolSize=10,但压测初期只看到 2–3 条线程在跑,怀疑配置没生效 → 实际是因为任务提交速率低,线程创建是按需触发的,不是一启动就拉满;
• 在 Spring Boot 中用 @Async 却发现线程数始终卡在 1 → 很可能 corePoolSize 被默认设为 1(如 SimpleAsyncTaskExecutor 伪线程池),而非真正的 ThreadPoolExecutor。
实操建议:
• CPU 密集型任务:设为 Runtime.getRuntime().availableProcessors() + 1;
• IO 密集型任务(如 HTTP 调用、DB 查询):可设为 2 * availableProcessors() 起步,再结合监控调优;
• 切忌硬编码,尤其在容器化部署中——CPU 核数可能被 cgroup 限制,应读取 /sys/fs/cgroup/cpu.max 或使用 Runtime.getRuntime().availableProcessors() 动态获取。
workQueue 不是“缓冲区”,而是“线程扩容开关”
workQueue 的作用远不止暂存任务:它直接决定线程池会不会创建非核心线程。只有当队列已满,且当前线程数 maximumPoolSize 时,才会扩容。所以选错队列,等于关掉了弹性能力。
常见错误现象:
• 用 LinkedBlockingQueue(无界)配小 corePoolSize,结果高并发下内存暴涨 OOM → 因为任务全堆在队列里,线程池永远不扩容;
• 用 SynchronousQueue 却没配够 maximumPoolSize,稍有波动就触发拒绝策略 → 它不存任务,相当于“必须立刻有线程接活”,对突发流量零容忍。
实操建议:
• 优先选 ArrayBlockingQueue,显式指定容量(如 256 或 1024),配合监控看队列积压率;
• 避免 LinkedBlockingQueue 不带容量参数(等价于 Integer.MAX_VALUE);
• SynchronousQueue 仅适用于任务极轻、延迟敏感、且能严格控流的场景(如网关转发)。
keepAliveTime 对非核心线程有效,但 corePoolSize 线程也能“退休”
默认情况下,corePoolSize 内的线程永不销毁,哪怕空闲一整天。但这是可打破的——调用 allowCoreThreadTimeOut(true) 后,所有线程(包括核心线程)都会遵守 keepAliveTime。这个开关常被忽略,却极大影响资源回收效率。
常见错误现象:
• 服务夜间流量归零,线程池仍维持 20 条线程 → 没开 allowCoreThreadTimeOut,导致资源长期闲置;
• 开了该选项却把 keepAliveTime 设成 1 秒 → 线程刚建好就销毁,频繁 GC + 创建开销反升。
实操建议:
• 业务流量有明显峰谷(如电商定时秒杀、报表生成),务必开启 allowCoreThreadTimeOut(true);
• keepAliveTime 设为 60 秒是安全起点,长周期任务(如文件导出)可延长至 300 秒;
• 时间单位必须匹配(如 keepAliveTime=60 + unit=TimeUnit.SECONDS),单位错位会导致线程“活不过 60 毫秒”。
handler 不是兜底逻辑,而是业务 SLA 的最后一道闸门
handler 不是“出了问题才看”,而是你对“不可服务状态”的明确定义。比如 AbortPolicy 直接抛 RejectedExecutionException,若上层没 catch,整个调用链就断了;而 CallerRunsPolicy 表面是“降级”,实际会把压力反传给调用方线程,可能拖垮 Web 容器主线程。
常见错误现象:
• 生产环境用默认 AbortPolicy,日志里满屏 RejectedExecutionException 却无人告警 → 缺少对拒绝事件的埋点;
• 用 DiscardPolicy 处理支付回调任务 → 订单状态停滞,用户投诉“已付款未发货”。
实操建议:
• 关键链路(支付、库存扣减)必须自定义 handler,记录日志 + 上报指标 + 触发告警;
• 非关键链路可用 CallerRunsPolicy,但需确保调用方线程具备足够栈空间和超时控制;
• 禁止在 handler 中做重试或远程调用——它运行在提交线程上下文,阻塞即放大雪崩风险。
立即学习“Java免费学习笔记(深入)”;
线程池不是配完就完的事。真正棘手的,是当服务器从 4C 升到 16C、从物理机迁到 K8s、从单体拆成微服务时,那些写死的 corePoolSize 和 ArrayBlockingQueue(1024) 会突然变成性能瓶颈——而错误往往藏在日志末尾一行被忽略的拒绝日志里。










