线程池是生产环境防止系统崩溃的刚需,而非可选优化;需合理配置核心参数、隔离任务类型、实施监控并正确关闭。

线程池不是“锦上添花”,而是防止系统崩掉的刚需
不使用线程池,意味着每次任务来都 new Thread().start() —— 短时间内几百个请求进来,就可能瞬间创建几百个线程。JVM 线程栈默认 1MB,光内存就吃掉几百 MB;再加上频繁的线程创建/销毁、上下文切换,CPU 使用率飙升,GC 压力暴涨,服务直接卡死或 OOM。线程池的核心价值,是把“资源失控”变成“可控复用”。这不是性能优化技巧,是生产环境的生存底线。
核心参数配错,比不用线程池还危险
很多人用 Executors.newFixedThreadPool(10) 图省事,但这个 API 底层用的是无界 LinkedBlockingQueue,一旦任务处理变慢(比如下游接口超时),队列无限堆积,最终堆内存耗尽。真正安全的配置必须显式控制边界:
-
corePoolSize:CPU 密集型任务建议设为Runtime.getRuntime().availableProcessors();IO 密集型可设为2 × CPU核心数或更高,但需配合压测验证 - 必须用有界队列,例如
new ArrayBlockingQueue(100),避免内存失控 -
maximumPoolSize要大于corePoolSize(否则扩容失效),但不能盲目设大——超过 200 的线程数在普通应用中往往已引发调度瓶颈 - 拒绝策略别用默认的
AbortPolicy(抛异常中断调用方),生产环境推荐CallerRunsPolicy:让提交线程自己执行任务,自然限流,比丢任务更可控
任务类型混用,一个线程池会拖垮整个系统
把 HTTP 请求处理、数据库批量写入、PDF 生成这三类任务扔进同一个线程池,等于让 CPU 密集型任务和 IO 阻塞型任务抢同一组线程。结果是:PDF 生成卡住 5 秒 → 所有线程被占满 → 新来的 HTTP 请求全部排队 → 接口超时雪崩。
正确做法是按任务特征隔离线程池:
立即学习“Java免费学习笔记(深入)”;
- Web 请求处理:小核心数 + 短超时 +
CallerRunsPolicy - DB 批量操作:单独池,连接池大小与之对齐,避免线程空等数据库连接
- 异步日志或通知:可配
CachedThreadPool(但注意其SynchronousQueue本质是“直接提交”,突发流量下会疯狂建线程,务必加监控)
没监控的线程池,就像没刹车的车
线程池是否健康,不能靠“没报错=正常”。必须实时关注三个指标:
-
getActiveCount()持续接近getPoolSize()→ 线程打满,任务开始排队 -
getQueue().size()持续增长 → 处理能力跟不上提交速度,得查下游或优化逻辑 -
getCompletedTaskCount()长期不增长 → 可能有任务未正确 submit,或线程因未捕获异常静默退出
这些值可通过 JMX 暴露,或集成 Micrometer + Prometheus。最常被忽略的一点:线程池 shutdown 后若仍有任务在跑,shutdownNow() 会中断正在执行的线程,但不会等待它们结束——如果你的任务没做中断响应(比如没检查 Thread.interrupted()),就可能造成数据不一致。









