当前位置: 首页 > news >正文

Java线程池参数配置的坑:`corePoolSize=0` + `LinkedBlockingQueue`直接变成串行执行

问题描述

在某项目代码中发现了很多这样的线程池的配置:

this.executorService =new ThreadPoolExecutor(0,10,60,TimeUnit.SECONDS,new LinkedBlockingQueue<>(),new ThreadFactoryBuilder().setNameFormat("Xxxx-%d").build());

其 corePoolSize 设置为0,而阻塞队列选择了 LinkedBlockingQueue,这是一个无界队列,默认大小是Integer.MAX_VALUE 2³¹-1(约21亿)。
这个配置的出发点是为了线程能够仅在需要时创建,从而避免没有业务请求的时候占用系统资源。只能说出发点是好的,但是问题就出在没有深刻理解线程池的线程创建机制,我们参考Java线程池实现原理及其在美团业务中的实践中的任务调度流程图:

示例图片
可以看到ThreadPoolExecutor的三大执行规则

  • 规则1:workerCount < corePoolSize ? 创建核心线程。
  • 规则2:workQueue.offer() ?任务入队。
  • 规则3:workerCount < maximumPoolSize ? 创建临时线程。

这里还有一个补充规则 零工作线程保护规则:在一个任务被成功放入队列之后,线程池会再做一次检查。如果此时发现线程池里一个工作线程都没有 (workerCount == 0),它会创建一个新的线程来确保队列里的任务能被及时处理。


现在,把新配置代入任务提交流程:

  1. 开始提交5000个任务。
  2. 提交第1个任务
    • 规则1 (0 < 0) 不满足。
    • 规则2 (workQueue.offer()) 成功。因为LinkedBlockingQueue是无界的,这个操作永远不会失败。任务1进入队列。
    • 零工作线程保护规则被触发:检查发现workerCount为0,于是创建一个新线程(Thread-1)。Thread-1启动后,从队列中取出任务1开始执行。
  3. 提交第2个任务 到 第5000个任务
    • 规则1 (1 < 0) 不满足。
    • 规则2 (workQueue.offer()) 永远成功。任务2、3、4…5000被源源不断地放入无界队列。
    • 补充规则不被触发,因为workerCount一直为1。
  4. 规则3永远不会被触发!
    • 因为规则2(任务入队)永远不会失败,所以程序流程永远没有机会进入到规则3。
    • 这意味着,maximumPoolSize这个参数在这种配置下完全失效了,形同虚设。线程池永远不会创建超过corePoolSize(在这里是1,因为零工作线程保护规则创建了一个)个线程。

结论:灾难性的结果

如果使用 corePoolSize=0 + LinkedBlockingQueue 的组合,系统将表现为:

无论提交多少任务,永远只有一个线程在工作。

由于上述配置中队列是无界的,任务会无限堆积在队列中,永远没有机会去触发“创建临时线程”的逻辑,maximumPoolSize也就成了摆设。

验证程序

下面是一个验证程序,可以通过手动更改其 corePoolSize 或 LinkedBlockingQueue 的大小来验证程序的执行情况:

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class Test {public static void main(String[] args) throws InterruptedException {// --- 1. 定义测试参数 ---final int TASK_COUNT = 20; // 提交的任务数量,特意设置为 maximumPoolSize 的两倍final int TASK_SLEEP_SECONDS = 1; // 每个任务的耗时(秒)// --- 2. 创建有问题的线程池配置 ---// 这是我们要测试的核心配置int corePoolSize = 0;int maximumPoolSize = 10;long keepAliveTime = 60;TimeUnit unit = TimeUnit.SECONDS;BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(); // 无界队列是关键// 自定义线程工厂,方便观察线程名ThreadFactory threadFactory = new ThreadFactory() {private final AtomicInteger threadNumber = new AtomicInteger(1);@Overridepublic Thread newThread(Runnable r) {return new Thread(r, "misconfigured-pool-thread-" + threadNumber.getAndIncrement());}};ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory);// --- 3. 打印测试说明 ---System.out.println("--- 线程池行为测试 ---");System.out.println("配置: corePoolSize=0, maximumPoolSize=10, workQueue=LinkedBlockingQueue (无界)");System.out.println("提交任务数: " + TASK_COUNT);System.out.println("每个任务耗时: " + TASK_SLEEP_SECONDS + " 秒");System.out.println("------------------------------------");System.out.println("预期结果分析:");System.out.println("  - 如果是10线程并行: 理想总耗时应约为 " +(TASK_COUNT / maximumPoolSize * TASK_SLEEP_SECONDS) + " 秒 (20任务/10线程 * 1秒)。");System.out.println("  - 如果是单线程串行: 理想总耗时应约为 " +(TASK_COUNT * TASK_SLEEP_SECONDS) + " 秒 (20任务 * 1秒)。");System.out.println("------------------------------------");System.out.println("测试开始,正在提交任务...");// --- 4. 执行测试 ---long startTime = System.currentTimeMillis();for (int i = 1; i <= TASK_COUNT; i++) {final int taskNumber = i;executor.submit(() -> {System.out.printf("任务 %2d 开始执行... | 由线程: %s%n",taskNumber, Thread.currentThread().getName());try {TimeUnit.SECONDS.sleep(TASK_SLEEP_SECONDS);} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.printf("任务 %2d 执行完毕。 | 队列中剩余任务数: %d%n",taskNumber, executor.getQueue().size());});}// --- 5. 关闭线程池并等待所有任务完成 ---executor.shutdown();// 等待足够长的时间以确保所有任务都执行完毕boolean finished = executor.awaitTermination(TASK_COUNT * TASK_SLEEP_SECONDS + 5, TimeUnit.SECONDS);long endTime = System.currentTimeMillis();long totalDurationSeconds = TimeUnit.MILLISECONDS.toSeconds(endTime - startTime);// --- 6. 打印并分析测试结果 ---System.out.println("------------------------------------");System.out.println("测试结束。所有任务已执行完毕。");System.out.println("实际总耗时: " + totalDurationSeconds + " 秒");System.out.println("------------------------------------");System.out.println("结论分析:");if (totalDurationSeconds >= TASK_COUNT) {System.out.println("✅ 实验结果符合预期:线程池表现为【单线程串行执行】。");System.out.println("原因:由于使用了无界的 LinkedBlockingQueue,任务会无限进入队列,");System.out.println("导致永远无法触发创建新线程(直到 maximumPoolSize)的逻辑。");System.out.println("maximumPoolSize=10 和 keepAliveTime=60 参数在此配置下完全失效。");} else {System.out.println("❌ 实验结果与理论不符,请检查测试环境或代码。");}}
}

实测结果

--- 线程池行为测试 ---
配置: corePoolSize=0, maximumPoolSize=10, workQueue=LinkedBlockingQueue (无界)
提交任务数: 20
每个任务耗时: 1 秒
------------------------------------
预期结果分析:- 如果是10线程并行: 理想总耗时应约为 2 秒 (20任务/10线程 * 1秒)。- 如果是单线程串行: 理想总耗时应约为 20 秒 (20任务 * 1秒)。
------------------------------------
测试开始,正在提交任务...
任务  1 开始执行... | 由线程: misconfigured-pool-thread-1
任务  1 执行完毕。 | 队列中剩余任务数: 19
任务  2 开始执行... | 由线程: misconfigured-pool-thread-1
任务  2 执行完毕。 | 队列中剩余任务数: 18
任务  3 开始执行... | 由线程: misconfigured-pool-thread-1
任务  3 执行完毕。 | 队列中剩余任务数: 17
任务  4 开始执行... | 由线程: misconfigured-pool-thread-1
任务  4 执行完毕。 | 队列中剩余任务数: 16
任务  5 开始执行... | 由线程: misconfigured-pool-thread-1
任务  5 执行完毕。 | 队列中剩余任务数: 15
任务  6 开始执行... | 由线程: misconfigured-pool-thread-1
任务  6 执行完毕。 | 队列中剩余任务数: 14
任务  7 开始执行... | 由线程: misconfigured-pool-thread-1
任务  7 执行完毕。 | 队列中剩余任务数: 13
任务  8 开始执行... | 由线程: misconfigured-pool-thread-1
任务  8 执行完毕。 | 队列中剩余任务数: 12
任务  9 开始执行... | 由线程: misconfigured-pool-thread-1
任务  9 执行完毕。 | 队列中剩余任务数: 11
任务 10 开始执行... | 由线程: misconfigured-pool-thread-1
任务 10 执行完毕。 | 队列中剩余任务数: 10
任务 11 开始执行... | 由线程: misconfigured-pool-thread-1
任务 11 执行完毕。 | 队列中剩余任务数: 9
任务 12 开始执行... | 由线程: misconfigured-pool-thread-1
任务 12 执行完毕。 | 队列中剩余任务数: 8
任务 13 开始执行... | 由线程: misconfigured-pool-thread-1
任务 13 执行完毕。 | 队列中剩余任务数: 7
任务 14 开始执行... | 由线程: misconfigured-pool-thread-1
任务 14 执行完毕。 | 队列中剩余任务数: 6
任务 15 开始执行... | 由线程: misconfigured-pool-thread-1
任务 15 执行完毕。 | 队列中剩余任务数: 5
任务 16 开始执行... | 由线程: misconfigured-pool-thread-1
任务 16 执行完毕。 | 队列中剩余任务数: 4
任务 17 开始执行... | 由线程: misconfigured-pool-thread-1
任务 17 执行完毕。 | 队列中剩余任务数: 3
任务 18 开始执行... | 由线程: misconfigured-pool-thread-1
任务 18 执行完毕。 | 队列中剩余任务数: 2
任务 19 开始执行... | 由线程: misconfigured-pool-thread-1
任务 19 执行完毕。 | 队列中剩余任务数: 1
任务 20 开始执行... | 由线程: misconfigured-pool-thread-1
任务 20 执行完毕。 | 队列中剩余任务数: 0
------------------------------------
测试结束。所有任务已执行完毕。
实际总耗时: 20 秒
------------------------------------
结论分析:
✅ 实验结果符合预期:线程池表现为【单线程串行执行】。
原因:由于使用了无界的 LinkedBlockingQueue,任务会无限进入队列,
导致永远无法触发创建新线程(直到 maximumPoolSize)的逻辑。
maximumPoolSize=10 和 keepAliveTime=60 参数在此配置下完全失效。

如果将LinkedBlockingQueue换为大小为10的队列:new LinkedBlockingQueue<>(10),那么表现如下:

测试开始,正在提交任务...
任务  1 开始执行... | 由线程: misconfigured-pool-thread-1
任务 20 开始执行... | 由线程: misconfigured-pool-thread-10
任务 19 开始执行... | 由线程: misconfigured-pool-thread-9
任务 18 开始执行... | 由线程: misconfigured-pool-thread-8
任务 17 开始执行... | 由线程: misconfigured-pool-thread-7
任务 16 开始执行... | 由线程: misconfigured-pool-thread-6
任务 15 开始执行... | 由线程: misconfigured-pool-thread-5
任务 14 开始执行... | 由线程: misconfigured-pool-thread-4
任务 13 开始执行... | 由线程: misconfigured-pool-thread-3
任务 11 开始执行... | 由线程: misconfigured-pool-thread-2
任务  1 执行完毕。 | 队列中剩余任务数: 10
任务 20 执行完毕。 | 队列中剩余任务数: 10
任务 19 执行完毕。 | 队列中剩余任务数: 9
任务  4 开始执行... | 由线程: misconfigured-pool-thread-9
任务 18 执行完毕。 | 队列中剩余任务数: 7
任务  5 开始执行... | 由线程: misconfigured-pool-thread-8
任务  2 开始执行... | 由线程: misconfigured-pool-thread-1
任务 15 执行完毕。 | 队列中剩余任务数: 6
任务 16 执行完毕。 | 队列中剩余任务数: 6
任务 17 执行完毕。 | 队列中剩余任务数: 7
任务 11 执行完毕。 | 队列中剩余任务数: 4
任务  9 开始执行... | 由线程: misconfigured-pool-thread-2
任务  3 开始执行... | 由线程: misconfigured-pool-thread-10
任务  8 开始执行... | 由线程: misconfigured-pool-thread-7
任务  7 开始执行... | 由线程: misconfigured-pool-thread-6
任务 13 执行完毕。 | 队列中剩余任务数: 5
任务 10 开始执行... | 由线程: misconfigured-pool-thread-3
任务  6 开始执行... | 由线程: misconfigured-pool-thread-5
任务 14 执行完毕。 | 队列中剩余任务数: 6
任务 12 开始执行... | 由线程: misconfigured-pool-thread-4
任务  4 执行完毕。 | 队列中剩余任务数: 0
任务  5 执行完毕。 | 队列中剩余任务数: 0
任务  2 执行完毕。 | 队列中剩余任务数: 0
任务  9 执行完毕。 | 队列中剩余任务数: 0
任务  3 执行完毕。 | 队列中剩余任务数: 0
任务  8 执行完毕。 | 队列中剩余任务数: 0
任务  7 执行完毕。 | 队列中剩余任务数: 0
任务 10 执行完毕。 | 队列中剩余任务数: 0
任务  6 执行完毕。 | 队列中剩余任务数: 0
任务 12 执行完毕。 | 队列中剩余任务数: 0
------------------------------------
测试结束。所有任务已执行完毕。
实际总耗时: 2 秒
------------------------------------

执行流程推演

初始状态core=0, max=10, queue capacity=10, workerCount=0

  1. main线程提交 Task 1

    • execute(task1) 被调用。
    • 规则1 (0 < 0) 不满足。
    • 规则2 (queue.offer(task1)) 成功。此刻,Task 1 进入了队列。队列大小为1。
    • “补充规则”触发!发现workerCount=0,于是开始创建 Thread-1。注意,只是“开始创建”,它还没启动完成。
  2. main线程继续提交 Task 2Task 10 (共9个任务)

    • main线程的速度极快,它在Thread-1还在“热身”的时候,就一口气把Task 2Task 10全部提交了。
    • 对于这9个任务,每次检查规则1 (workerCount < 0) 都不满足。
    • 规则2 (queue.offer()) 每次都成功
    • 此刻状态Task 1Task 10 共10个任务,全部躺在了队列里。队列已满!workerCount可能仍然是1(因为Thread-1正在创建中)。
  3. 引爆点!main线程提交 Task 11

    • execute(task11) 被调用。
    • 规则1 (workerCount < 0) 不满足。
    • 规则2 (queue.offer(task11)) 失败! 因为队列在第2步中已经被填满了。
    • 流程进入规则3!
    • 检查 workerCount < max (1 < 10)?满足!
    • 线程池决定Task 11 创建一个新的救急线程。这个新线程就是 Thread-2Thread-2被创建时,是直接绑定了Task 11的!
  4. main线程继续提交 Task 12Task 20

    • 这个过程和第3步完全一样。
    • submit(task12) -> 队列满 -> 创建Thread-3执行Task 12
    • submit(task20) -> 队列满 -> 创建Thread-10执行Task 20
http://www.dtcms.com/a/339930.html

相关文章:

  • Python爬虫第二课:爬取HTML静态网页之《某某小说》 小说章节和内容完整版
  • 智驾-AEB
  • 羟氯喹通过抑制抗磷脂综合征诱导的绒毛外滋养细胞过度自噬
  • 【模版匹配】基于深度学习
  • 洛谷 P2834 纸币问题 3-普及-
  • 《当 AI 学会 “思考”:大语言模型的逻辑能力进化与隐忧》
  • centos 总有new mail出现原因
  • [论文阅读] 软件工程 - 用户体验 | VR应用的无障碍性困局:基于Meta和Steam商店评论的深度剖析
  • 多幅图片拼接算法系统
  • FIFO通讯速率> 30MB/s,CH346保障FPGA与PC的高速通道稳定高效
  • 当GitHub宕机时,我们如何协作
  • 工业4.0时代,耐达讯自动化Profibus转光纤如何重构HMI通信新标准?“
  • HTML应用指南:利用GET请求获取全国新荣记门店位置信息
  • 【DAB收音机】DAB服务跟随Service Follow功能(三)【FIG 0/24:OE Services】
  • Browser Use + Playwright到AI Agent:Web自动化如何实现质变?
  • C++装饰器模式:从“勇勇”例子到实际应用
  • Day09 Go语言深入学习(1)
  • 单片机编程架构
  • AttributeError: module ‘ffmpeg‘ has no attribute ‘probe‘
  • 【光学神经网络与人工智能应用专题】
  • 力扣hot100:三数之和(排序 + 双指针法)(15)
  • 深度学习-167-MCP技术之工具函数的设计及注册到MCP服务器的两种方式
  • 零售行业新店网络零接触部署场景下,如何选择SDWAN
  • 排查Redis数据倾斜引发的性能瓶颈
  • 缓存-变更事件捕捉、更新策略、本地缓存和热key问题
  • Autoware Universe 感知模块详解 | 第零节 如何学习开源框架(以Autoware Universe为例)
  • 新手入门:用 LangChain+LlamaIndex 构建 RAG,通义千问 API 免费够用
  • 机器人控制基础:串级 PID 和模糊 PID介绍与对比(干货总结)
  • Java 大视界 -- Java 大数据在智能物流无人配送车路径规划与协同调度中的应用
  • [激光原理与应用-303]:光学设计 - 光路设计的输出件