吃烤鱼时突然悟到的:为什么 Java 线程池的扩容逻辑是“反直觉”的?

今天去吃烤鱼,排队的时候我突然陷入了思考。

场景复现:这家店生意很火。

  1. 店里原本有 10 张桌子(核心线程),全坐满了。
  2. 老板看到还有人来,立马把门口露天的 5 张折叠桌也支棱起来了(最大线程)。
  3. 折叠桌也坐满了,老板才开始发号,让大家在门口坐小板凳排队(队列)。
  4. 排队的人实在太多,老板只能出来挥手:"别排了,今天的鱼卖完了"(拒绝策略)。

这逻辑很顺吧?
先尽全力接待(扩容),实在不行了再让人等(排队)。

可我转念一想------

Java 原生线程池(ThreadPoolExecutor)的逻辑怎么是反着来的?

它的流程是:
核心线程满 → 进队列排队 → 队列满了才去扩容(创建最大线程)

这就好比:店里 10 张桌子坐满了,老板不先支折叠桌,而是非要让客人在门口排队。

直到门口小板凳都坐不下了,才不情不愿地去支折叠桌。

这不是反人类吗?

回来后我翻了源码,终于想通了其中的博弈。

1. 核心矛盾:资源视角的差异

烤鱼店视角(IO 密集型 / 响应优先)

  • 目标:赚钱,不让客人跑了
  • 成本:折叠桌(线程)很便宜,客人等久了会走
  • 策略:保响应,只要有空地,赶紧把桌子支起来,别让客人干等

Java JDK 视角(计算密集型 / 资源优先)

  • 目标:保护 CPU,防止系统崩盘
  • 成本:线程是昂贵资源
    • 创建一个线程默认占 1MB 栈空间(栈)
    • 线程太多,上下文切换会把 CPU 拖死
  • 队列是廉价资源:一个任务对象扔堆里,几乎不花钱
  • 策略:省资源,只要核心线程能扛,就绝不轻易加人
    "先让任务在内存里排会儿队吧,实在扛不住我再加人。"

2. 破局:Tomcat 的"烤鱼流"线程池

难道 Java 就不能像烤鱼店一样工作吗?

能,而且 Tomcat/Jetty 就是这么干的。

Web 请求是典型的 IO 密集型任务(读 DB、调接口,CPU 经常空闲)。

如果按 JDK 默认逻辑(先排队),用户在浏览器前就等到死。

Tomcat 的骚操作:

它继承了 ThreadPoolExecutor,但魔改了队列(TaskQueue)。

  • 核心线程满了,任务想入队时,队列会伪造"我已满"的假象(offer 返回 false)
  • 线程池一听"队列满了",立刻创建新线程(支折叠桌)
  • 只有当线程数真的达到 maximumPoolSize,队列才会真正接收任务开始排队

3. JDK 默认线程池的世纪大坑

初学者喜欢这么写:

java 复制代码
Executors.newFixedThreadPool(10)

展开其实是:

java 复制代码
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>()); // ⚠️ 坑在这里
}

LinkedBlockingQueue 是无界队列(默认 Integer.MAX_VALUE)

后果:

  1. 队列永远装不满 → maximumPoolSize 参数直接失效(永远轮不到它出场)
  2. 任务处理不过来时,队列无限膨胀 → OOM → 服务直接挂掉

这才是真正的生产事故之王。

4. 总结与避坑指南

两种逻辑对比:

场景 执行顺序 适用场景
JDK 默认 核心 → 队列 → 最大 → 拒绝 后台计算任务、保护 CPU
烤鱼店/Tomcat 核心 → 最大 → 队列 → 拒绝 高并发 Web、追求低延迟

生产环境铁律:

  1. 禁止使用 Executors 的快捷方法创建线程池
  2. 必须手动 new ThreadPoolExecutor(...)
  3. 必须用有界队列(推荐 ArrayBlockingQueue
  4. 拒绝策略推荐 CallerRunsPolicy(让调用者线程自己干活,实现天然背压)

下次你去吃烤鱼排队时,注意看老板是先支折叠桌,还是先让你们排队。

如果他先支折叠桌------

恭喜你,这老板懂高并发,比很多写 Java 的都强。

相关推荐
v***8571 小时前
Spring Boot 集成 MyBatis 全面讲解
spring boot·后端·mybatis
武昌库里写JAVA2 小时前
Java如何快速入门?Java基础_Java入门
java·vue.js·spring boot·后端·sql
程序员爱钓鱼2 小时前
Python职业路线规划:从入门到高级开发者的成长指南
后端·python·trae
程序员爱钓鱼2 小时前
Python 编程实战 · 进阶与职业发展:自动化运维(Ansible、Fabric)
后端·python·trae
风的归宿552 小时前
gitlab配置ai代码审核
后端
格格步入2 小时前
线上问题:MySQL NULL值引发的投诉
后端·mysql
Moe4882 小时前
Spring Boot 自动配置核心:AutoConfigurationImportSelector 深度解析
java·后端·设计模式
武子康3 小时前
大数据-161 Apache Kylin Cube 实战:建模、构建与查询加速完整指南
大数据·后端·apache kylin
踏浪无痕3 小时前
准备手写Simple Raft(三) 日志复制——一致性检查
后端·raft