《Java 100 天进阶之路》第55篇:线程池ThreadPoolExecutor(2026版)

《Java 100 天进阶之路》第55篇:线程池ThreadPoolExecutor(2026版)
第55篇线程池ThreadPoolExecutor2026版系列导航《Java 100 天进阶之路》完整目录 |⬅️ 上一篇第54篇AQS抽象队列同步器 |➡️ 下一篇第56篇死锁与排查工具待发布️ 本文阅读地图3 分钟速览第54篇拿下了 AQS本篇深入并发编程最常用的工具——线程池。掌握了 ThreadPoolExecutor你就掌握了 Java 并发编程的“生产力工具”模块核心问题一句话回答七大参数线程池由哪些参数控制corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler工作流程任务提交后怎么处理核心线程 → 任务队列 → 非核心线程 → 拒绝策略四种拒绝策略线程池满了怎么办AbortPolicy抛异常、CallerRunsPolicy调用者执行、DiscardPolicy丢弃、DiscardOldestPolicy丢弃最旧Executors 陷阱为什么大厂禁止用 Executors无界队列 → OOM无限线程 → 资源耗尽面试最爱问高频考点有哪些见文末 小节文章目录第55篇线程池ThreadPoolExecutor2026版️ 本文阅读地图3 分钟速览一、核心知识点二、生活类比从“互联网公司”到“线程池”三、七大核心参数详解3.1 corePoolSize核心线程数3.2 maximumPoolSize最大线程数3.3 keepAliveTime unit空闲存活时间3.4 workQueue工作队列3.5 threadFactory线程工厂3.6 handler拒绝策略四、任务执行流程面试核心4.1 完整流程图4.2 关键理解4.3 execute() vs submit()4.4 线程池生命周期管理五、Executors 的陷阱为什么大厂禁用5.1 三大陷阱5.2 正确做法手动创建 ThreadPoolExecutor六、生产级避坑清单七、面试高频考点面试官追问陷阱加分题八、练习题 你的学习进度 下一篇文章预告一、核心知识点线程池是什么线程池是一种池化资源管理技术通过预先创建一定数量的线程并统一管理实现任务的异步执行与资源复用。四大核心优势优势说明降低资源消耗复用已创建的线程避免频繁创建/销毁的系统开销提高响应速度任务到达时无需等待线程创建即可执行提高线程可管理性统一分配、调优和监控线程资源提供附加功能定时执行、并发控制、任务排队等核心设计思想基于生产者-消费者模型——提交任务的线程是生产者线程池中的工作线程是消费者任务队列BlockingQueue是缓冲区。七大核心参数publicThreadPoolExecutor(intcorePoolSize,// 1. 核心线程数intmaximumPoolSize,// 2. 最大线程数longkeepAliveTime,// 3. 空闲线程存活时间TimeUnitunit,// 4. 时间单位BlockingQueueRunnableworkQueue,// 5. 工作队列ThreadFactorythreadFactory,// 6. 线程工厂RejectedExecutionHandlerhandler// 7. 拒绝策略)二、生活类比从“互联网公司”到“线程池”线程池就像一家互联网公司的人力资源管理corePoolSize核心线程数公司的正式员工。只要公司不倒闭即使没活干也不会轻易辞退。maximumPoolSize最大线程数公司能容纳的总人数正式员工 临时工。当正式员工都在忙任务积压时公司会招聘临时工。workQueue任务队列公司的待办任务池。正式员工忙不过来时新任务先放进待办池排队。非核心线程临时工任务池满了正式员工忙不过来公司开始招临时工帮忙。任务高峰期过去临时工空闲太久就会被裁掉keepAliveTime。拒绝策略正式员工、临时工都在忙待办任务池也满了新任务来了怎么办公司有四种应对方案见下文。线程池初始化时不会立即创建核心线程而是在任务提交时懒加载创建正式员工是“入职后才开始干活”。三、七大核心参数详解3.1 corePoolSize核心线程数定义线程池中长期维持的最小线程数量即使空闲也不会被销毁。核心行为默认情况下核心线程会一直存活提交新任务时如果当前运行线程数 corePoolSize立即创建新线程执行任务即使其他核心线程空闲可通过allowCoreThreadTimeOut(true)允许核心线程超时销毁设置建议CPU 密集型计算为主CPU 核数 1IO 密集型网络/磁盘等待为主CPU 核数 * 2或CPU 核数 / (1 - 阻塞系数)3.2 maximumPoolSize最大线程数定义线程池允许创建的最大线程数量。核心行为当核心线程已满且工作队列已满时线程池会创建非核心线程直到达到 maximumPoolSize包含核心线程和非核心线程两部分注意事项maximumPoolSize 必须 corePoolSize如果队列是无界队列如LinkedBlockingQueue默认该参数失效因为队列永远不会满3.3 keepAliveTime unit空闲存活时间定义非核心线程空闲后的存活时间。核心行为当线程池中的线程数量超过 corePoolSize 时多余的空闲线程在终止前等待新任务的最长时间若设置allowCoreThreadTimeOut(true)核心线程也会受此参数影响3.4 workQueue工作队列定义用于存储等待执行任务的阻塞队列。常见队列对比队列类型特点适用场景风险点ArrayBlockingQueue有界阻塞队列基于数组FIFO任务量可控需限制队列长度需合理设置队列大小LinkedBlockingQueue可选有界默认无界基于链表流量平稳的后台任务无界队列 → 内存溢出风险SynchronousQueue不存储元素直接交给线程高吞吐、低延迟场景需配合足够线程数PriorityBlockingQueue支持优先级的无界阻塞队列需按优先级执行任务无界队列 → OOM 风险DelayQueue延迟队列元素到期后才能被获取定时任务、超时处理无界队列 → OOM 风险生产环境建议避免使用无界队列推荐设置队列容量为max(核心线程数, 预期峰值 QPS × 平均处理时间)对于突发流量场景可采用SynchronousQueue 弹性扩容策略3.5 threadFactory线程工厂定义创建线程的工厂。核心作用默认是Executors.defaultThreadFactory()自定义时可设置线程名称、是否为守护线程等自定义示例ThreadFactorynamedFactoryr-newThread(r,biz-pool-%d);// 或使用 GuavaThreadFactorynamedFactorynewThreadFactoryBuilder().setNameFormat(order-process-pool-%d).build();3.6 handler拒绝策略定义当线程池已满队列满了 线程数已达 maximum且还有新任务时触发的处理策略。四种内置拒绝策略策略行为适用场景AbortPolicy抛出RejectedExecutionException默认需要明确感知任务被拒绝CallerRunsPolicy由调用者线程执行该任务不能丢失任务可接受调用者阻塞DiscardPolicy静默丢弃新任务不抛异常可容忍部分任务丢失如日志DiscardOldestPolicy丢弃队列中最旧的任务再提交新任务需要最新任务优先执行AbortPolicy 是默认策略如果不想任务丢失应显式设置CallerRunsPolicy。四、任务执行流程面试核心4.1 完整流程图线程池执行execute()的四步流程4.2 关键理解⚠️重要队列满了才创建非核心线程而不是先扩充到 maximumPoolSize。设计逻辑核心线程是“常驻员工”任务是“待办池”非核心线程是“临时工”。只有待办池满了正式员工忙不过来才招临时工。这避免了过度创建线程带来的资源浪费。边界情况当corePoolSize maximumPoolSize时退化为固定大小线程池设置maximumPoolSize为Integer.MAX_VALUE时允许无限扩容需谨慎使用无界队列时maximumPoolSize参数失效队列永远不会满4.3 execute() vs submit()对比execute(Runnable)submit(Callable/Runnable)返回值❌ 无返回值✅ 返回FutureT异常处理任务异常直接抛出到控制台异常封装在 Future 中调用get()时抛出适用场景不需要返回结果的任务需要获取执行结果或处理异常4.4 线程池生命周期管理方法说明shutdown()优雅关闭——等待已提交任务完成不再接受新任务shutdownNow()立即关闭——尝试中断运行中任务返回未执行任务列表isShutdown()判断是否已关闭isTerminated()判断是否已终止awaitTermination(timeout, unit)阻塞等待线程池终止五、Executors 的陷阱为什么大厂禁用阿里巴巴《Java开发手册》明确禁止在生产环境使用Executors工厂方法创建线程池。5.1 三大陷阱Executors 方法隐藏的问题后果newFixedThreadPool(n)使用无界 LinkedBlockingQueue容量Integer.MAX_VALUE任务堆积 →OOMnewSingleThreadExecutor()同上且单线程处理积压风险更高 →OOMnewCachedThreadPool()最大线程数Integer.MAX_VALUE无限创建线程高并发下创建海量线程 →资源耗尽 / CPU 100%newScheduledThreadPool(n)同样存在线程数失控风险OOM 或资源耗尽⚠️结论Executors 的便利背后是隐患——“无界队列 固定线程 任务无限堆积 → OOM”“无界线程数 系统资源被耗尽 → OOM 或 CPU 打满”。5.2 正确做法手动创建 ThreadPoolExecutorExecutorServiceexecutornewThreadPoolExecutor(10,// corePoolSize20,// maximumPoolSize60L,TimeUnit.SECONDS,// keepAliveTimenewArrayBlockingQueue(100),// ✅ 有界队列防止OOMnewThreadFactoryBuilder()// ✅ 自定义线程名.setNameFormat(biz-pool-%d).build(),newThreadPoolExecutor.AbortPolicy()// ✅ 明确拒绝策略);六、生产级避坑清单✅ 线程池生产环境使用规范 1. 禁止使用 Executors 创建线程池 → 手动创建 ThreadPoolExecutor 2. 必须使用有界队列 → ArrayBlockingQueue 或指定容量的 LinkedBlockingQueue 3. 自定义线程名称 → ThreadFactory 设置线程名便于日志追踪和问题排查 4. 根据场景选择合适的拒绝策略 → 不能丢任务用 CallerRunsPolicy 5. 线程数配置参考 - CPU 密集型CPU 核数 1 - IO 密集型CPU 核数 * 2 或更高 6. 务必在 finally 中关闭线程池 → 使用 shutdown() awaitTermination() 7. 任务中必须捕获异常 → 否则异常会丢失线程被回收七、面试高频考点Q1ThreadPoolExecutor 的七大参数是什么各自的作用corePoolSize核心线程数、maximumPoolSize最大线程数、keepAliveTime空闲存活时间、unit时间单位、workQueue工作队列、threadFactory线程工厂、handler拒绝策略。核心线程是“常驻员工”非核心线程是“临时工”工作队列是“待办池”。Q2任务提交后线程池的执行流程三步判断① 当前线程数 corePoolSize → 创建核心线程执行② 队列未满 → 放入队列等待③ 线程数 maximumPoolSize → 创建非核心线程执行④ 否则执行拒绝策略。队列满了才创建非核心线程。Q3为什么禁止使用 Executors 创建线程池newFixedThreadPool和newSingleThreadExecutor使用无界 LinkedBlockingQueue任务堆积会导致OOM。newCachedThreadPool最大线程数为Integer.MAX_VALUE高并发下会创建海量线程导致系统资源耗尽或 CPU 100%。生产环境必须手动创建 ThreadPoolExecutor使用有界队列。Q4线程池的拒绝策略有哪些四种内置策略① AbortPolicy抛异常默认② CallerRunsPolicy调用者线程执行可防止任务丢失③ DiscardPolicy静默丢弃④ DiscardOldestPolicy丢弃最旧任务。Q5corePoolSize 和 maximumPoolSize 如何设置CPU 密集型corePoolSize CPU 核数 1减少上下文切换。IO 密集型corePoolSize CPU 核数 * 2 或更高利用等待 IO 的时间片。maximumPoolSize 根据业务峰值和系统资源调整一般为核心线程数的 2-4 倍。必须配合有界队列否则 maximumPoolSize 失效。面试官追问陷阱加分题追问1“LinkedBlockingQueue无界时maximumPoolSize还有意义吗”没有意义。无界队列永远不会满线程池永远不会创建非核心线程。maximumPoolSize和拒绝策略都失效只有 corePoolSize 在工作。这是Executors.newFixedThreadPool()的致命问题。追问2“CallerRunsPolicy有什么副作用什么时候用” 调用者线程执行任务会阻塞调用者可能导致上游系统超时。适用于不能丢失任务的场景如支付回调、订单确认且调用者能承受阻塞。追问3“线程池中的线程抛异常会怎样” 任务抛出未捕获异常时线程会被回收Worker 退出线程池会创建新线程补充。这也是为什么需要在任务中自行捕获异常——否则任务失败不会重试且线程创建销毁有额外开销。八、练习题分析题用Executors.newFixedThreadPool(10)创建线程池提交大量任务后为什么会 OOM 思路newFixedThreadPool底层使用无界 LinkedBlockingQueue任务处理速度跟不上提交速度时任务不断在队列中堆积最终耗尽堆内存。代码题手动创建一个线程池要求核心线程数 5、最大线程数 20、空闲存活 60 秒、有界队列容量 100、拒绝策略为 CallerRunsPolicy、自定义线程名“biz-pool-%d”。场景设计某支付系统需要异步处理订单任务绝对不能丢失且系统对延迟不敏感。应该选择什么拒绝策略为什么 思路选择CallerRunsPolicy。调用者线程直接执行任务确保任务不会丢失。虽然会阻塞调用者但对延迟不敏感的系统可以接受。 你的学习进度当前第55篇 / 共108篇 ·进阶篇并发编程与JUC详解第51~60篇✅ 已完成基础篇44篇 第45~55篇 正在学第55篇⏳ 待学习第56~108篇 完整目录 学习指南 | 订阅本专栏不错过每一篇 下一篇文章预告下一篇《第56篇死锁与排查工具》内容简介死锁的四大必要条件、jstack和jconsole排查工具、代码示例、预防与避免策略、生产环境实战案例。多线程专题持续深入拿下死锁排查《Java 100 天进阶之路 | 从入门到上岗就业》每天一篇建议收藏 关注一起100天拿offer 点击关注我更新后第一时间收到推送