关键在于严格分离线程池用途:阻塞I/O任务必须提交到专用线程池,NIO线程须保持非阻塞;需配置有界队列、合理拒绝策略(如CallerRunsPolicy)、超时控制及监控告警。

关键不是“弹性”,而是别让阻塞 I/O 污染 I/O 线程池。线程数暴增的根本原因,是任务在执行时发生阻塞,导致线程池误判为“忙不过来”,不断扩容——尤其当使用 newCachedThreadPool 或 maximumPoolSize 过大 + 无界队列时,极易触发指数级线程创建。
严格分离线程池用途:I/O 专用池不跑业务逻辑
把含阻塞 IO(如 JDBC 查询、HTTP 同步调用、文件读写)的任务,**单独提交到专为阻塞操作设计的线程池**,而不是混进处理网络事件或轻量计算的池里。
- Web 容器的 NIO 线程(如 Tomcat 的
http-nio-8080-exec)必须保持非阻塞:里面绝不能调用RestTemplate.getForObject()或connection.prepareStatement() - 为阻塞 IO 单独建池,例如:
`new ThreadPoolExecutor(4, 16, 30L, TimeUnit.SECONDS, new ArrayBlockingQueue(200), r -> new Thread(r, "db-worker-%d"))` - 命名线程池线程(如
"db-worker"),便于 jstack 快速识别阻塞源头
强制设置有界队列 + 合理拒绝策略
无界队列(如 LinkedBlockingQueue())会让任务持续堆积,而线程池又不敢轻易扩容(因为核心线程还没满),结果就是等队列撑爆内存;但若队列太小又容易过早拒绝。平衡点在于:
- 用
ArrayBlockingQueue或带容量的LinkedBlockingQueue(500),明确上限 - 拒绝策略别用默认
AbortPolicy(抛异常中断流程),推荐:
–CallerRunsPolicy:让调用方线程自己执行,自然压慢上游提交速度
– 或自定义策略:记录日志 + 上报监控(如 Prometheus Counter)
给阻塞操作加超时,从根源缩短线程占用时间
即使进了专用池,单个阻塞任务耗时过长,也会拖慢整个池的周转率。必须对所有外部依赖设限:
- JDBC:设置
socketTimeout和connectTimeout(如 MySQL 的connectTimeout=3000&socketTimeout=10000) - HTTP 客户端:OkHttp 设置
callTimeout,RestTemplate 配ClientHttpRequestFactory带超时 - 文件/本地资源:用
try-with-resources+ 显式 close,避免句柄泄漏锁死线程
监控与兜底:及时发现并熔断异常增长
光靠配置不够,得看得见、控得住:
- 暴露关键指标:活跃线程数、队列长度、拒绝任务数(可通过
ThreadPoolExecutor的getActiveCount()、getQueue().size()等方法接入 Micrometer) - 设置告警阈值:比如“连续 1 分钟活跃线程 > corePoolSize × 3” 或 “拒绝率 > 1%” 触发企业微信/钉钉告警
- 极端情况下,可动态降级:通过开关关闭非核心阻塞调用,或切换为异步补偿(如写 MQ 后台处理)

















