corepoolsize应设为平均持续活跃线程数(如db池10~20、http处理4~8),maximumpoolsize需兼顾jvm栈内存与os调度能力,避免integer.max_value;keepalivetime对无界队列无效,有界队列宜设60秒;拒绝策略须按业务重要性选型,禁用静默丢弃,推荐自定义告警策略。

corePoolSize 和 maximumPoolSize 到底怎么设才不翻车
设错这两个值,线程池要么常年空转吃资源,要么一到高峰就疯狂创建线程然后 OOM。关键不是“理论最大并发数”,而是你任务的**阻塞特征**和**系统承载能力**。
-
corePoolSize建议按「平均持续活跃线程数」设,比如数据库连接池常用 10~20,HTTP 请求处理常见 4~8(取决于 I/O 密集程度) -
maximumPoolSize不是越大越好:JVM 线程栈默认 1MB,开 500 个线程光栈就占 500MB;超过 OS 能高效调度的线程数(通常几百以内),吞吐反而下降 - 如果用
Executors.newCachedThreadPool(),maximumPoolSize是Integer.MAX_VALUE,这是生产环境雷区——突发流量进来直接内存打满或线程创建失败抛OutOfMemoryError: unable to create native thread
keepAliveTime 设太短 or 太长都会出问题
这个参数只对超出 corePoolSize 的空闲线程生效,但很多人忽略了它和队列类型之间的隐含冲突。
- 用
LinkedBlockingQueue(无界队列)时,keepAliveTime基本无效——因为永远不会有“超出核心数的线程”被创建出来(任务全塞队列里了) - 用
ArrayBlockingQueue(有界队列)+ 较小keepAliveTime(如 60L,TimeUnit.SECONDS),能更快释放非核心线程,避免低峰期资源闲置 - 设成 0L 表示“空闲即销毁”,但要注意:频繁创建/销毁线程本身有开销,不适合短生命周期、高频率提交的场景
拒绝策略选错,等于把错误藏起来等爆炸
默认的 AbortPolicy 直接抛 RejectedExecutionException,看似暴力,其实是给你明确信号:系统扛不住了。但很多人换成 CallerRunsPolicy 就以为万事大吉,其实埋了坑。
-
DiscardPolicy和DiscardOldestPolicy静默丢任务,适合日志采集类非关键操作;业务核心链路绝对不能用 -
CallerRunsPolicy让调用线程自己执行任务,短期缓解压力,但会拖慢上游——如果上游是 Web 容器线程(如 Tomcat 的http-nio-8080-exec-xx),等于把压力反灌给 HTTP 线程池,可能造成请求超时雪崩 - 真正可控的做法是自定义策略:记录日志 + 上报监控 + 触发告警,比如写个
LoggingRejectedExecutionHandler,别只是打印一句 “rejected”
为什么用了 SynchronousQueue 还是卡住?
SynchronousQueue 本身不存储任务,必须有空闲线程立刻接手,否则就触发拒绝策略。但它常被误用在“想省内存”的场景,结果反而更脆。
立即学习“Java免费学习笔记(深入)”;
- 搭配
corePoolSize = 0+maximumPoolSize = N,本质是“来一个任务启一个线程”,线程生命周期极短,但创建开销大;适合偶发、耗时长、彼此隔离的任务(如异步发邮件) - 如果任务执行时间波动大(比如有的 10ms,有的 5s),而
maximumPoolSize又设得太小,就会大量触发拒绝——因为没线程可交棒 - 注意 JVM 参数影响:
-Xss越小,能创建的线程越多,但太小(如 128k)可能导致深度递归栈溢出;建议保持默认 1M 或谨慎调到 512k
最常被忽略的其实是队列容量和拒绝策略的组合效果:比如 ArrayBlockingQueue(100) + maximumPoolSize=10,意味着最多排队 100 个、同时跑 10 个,但一旦队列满且线程数已达上限,下一个任务就立刻被拒——这个临界点比想象中来得快得多。










