文章目录
- JDK系列10:线程池ThreadPoolExecutor源码,核心参数、拒绝策略业务落地
- 一、前言:为什么必须手动用ThreadPoolExecutor?
- 二、线程池核心设计思想
- 2.1 为什么要用线程池?
- 2.2 线程池核心优势
- 三、ThreadPoolExecutor七大核心参数(源码级详解)
- 3.1 完整构造方法
- 3.2 七大参数逐字拆解
- 1)corePoolSize:核心线程数
- 2)maximumPoolSize:最大线程数
- 3)keepAliveTime:空闲超时时间
- 4)unit:时间单位
- 5)workQueue:任务阻塞队列
- 6)threadFactory:线程工厂
- 7)handler:拒绝策略
- 3.3 核心参数面试高频总结
- 四、ThreadPoolExecutor核心源码执行流程
- 4.1 线程池核心状态(源码常量)
- 4.2 execute\(\)核心方法源码流程
- 步骤1:判断核心线程数
- 步骤2:核心线程已满,入队排队
- 步骤3:队列已满,创建非核心线程
- 步骤4:触发拒绝策略
- 4.3 源码核心逻辑精简总结
- 五、四种内置拒绝策略源码+适用场景
- 5.1 AbortPolicy(默认策略)—— 直接抛出异常
- 5.2 CallerRunsPolicy(调用者运行策略)—— 退化为同步执行
- 5.3 DiscardPolicy(丢弃策略)—— 静默丢弃任务
- 5.4 DiscardOldestPolicy(丢弃最旧任务策略)
- 六、生产级自定义拒绝策略(业务核心落地)
- 6.1 自定义拒绝策略实战代码
- 6.2 自定义策略业务价值
- 七、生产级线程池完整配置方案(可直接复用)
- 八、线程池参数调优核心公式(面试+架构必备)
- 8.1 CPU密集型任务
- 8.2 IO密集型任务(业务主流)
- 九、高频面试题+生产避坑总结
- 9.1 经典面试问答
- 9.2 生产避坑核心要点
- 十、全文总结
JDK系列10:线程池ThreadPoolExecutor源码,核心参数、拒绝策略业务落地
📌 专栏:JDK核心进阶系列
🎯 文章评级:95分(源码深挖+参数详解+业务落地+面试全覆盖)
🔥 适用人群:Java后端开发、架构设计、面试突击、生产问题排查
💡 核心看点:摒弃网上碎片化浅度讲解,从线程池设计思想、ThreadPoolExecutor源码层级入手,透彻解析七大核心参数、四种拒绝策略、线程池工作原理、执行流程、源码核心方法,结合真实业务场景落地最佳实践,解决生产线程池参数乱配、任务堆积、OOM、拒绝策略滥用等高频问题。
一、前言:为什么必须手动用ThreadPoolExecutor?
在上一篇JDK多线程基础中,我们讲到线程池是生产环境首选的线程创建方式。JDK提供了Executors工具类快速创建线程池,但在阿里巴巴Java开发手册中强制禁止使用Executors创建线程池。
核心原因:Executors封装过于简化,屏蔽了线程池底层细节,不同场景下会出现任务堆积、线程无限创建、OOM内存溢出、拒绝策略不匹配业务等严重生产问题。
真正的生产级线程池,全部基于ThreadPoolExecutor原生构造器手动创建。本文将从设计思想→源码解析→核心参数→执行流程→拒绝策略→业务落地→调优避坑全链路吃透线程池核心,彻底搞定面试+生产双重场景。
二、线程池核心设计思想
2.1 为什么要用线程池?
手动new Thread的两大致命缺陷:
资源开销大:线程创建、销毁需要调用操作系统内核态接口,频繁创建销毁会造成巨大CPU开销;
不可控:无限制创建线程会导致线程数量溢出、CPU上下文切换爆炸、程序卡死。
线程池的核心设计理念:线程复用、资源可控、任务解耦、统一调度。提前初始化线程,任务到来直接复用,任务结束线程不销毁,极大提升并发处理效率。
2.2 线程池核心优势
降低资源消耗:复用线程,避免频繁创建销毁的系统开销;
提升响应速度:任务到达无需等待线程创建,直接执行;
统一管控:统一管理线程生命周期、任务队列、并发数量;
具备过载保护:通过队列+拒绝策略,防止高并发流量打垮服务。
三、ThreadPoolExecutor七大核心参数(源码级详解)
线程池所有特性、性能、容错能力,全部由七大构造参数决定,这是线程池最核心、面试最高频的知识点。先看懂参数,再看源码流程。
3.1 完整构造方法
publicThreadPoolExecutor(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,BlockingQueue<Runnable>workQueue,ThreadFactorythreadFactory,RejectedExecutionHandlerhandler)3.2 七大参数逐字拆解
1)corePoolSize:核心线程数
线程池中长期存活的常驻线程数,即使线程空闲也不会被回收。
核心规则:线程池初始化后,默认无线程;任务到来时逐步创建线程,直到线程数达到corePoolSize。日常业务中,核心线程是线程池的基础运力。
2)maximumPoolSize:最大线程数
线程池允许创建的最大线程总数。当任务量暴增、队列已满时,线程池会新建非核心线程,直到总线程数达到maximumPoolSize。
计算公式:非核心线程数 = 最大线程数 - 核心线程数
注意:非核心线程空闲超时后会被回收,节省系统资源。
3)keepAliveTime:空闲超时时间
非核心线程的空闲存活时间。当非核心线程空闲时间超过该值,会被自动回收。
特殊规则:JDK1.6+开启allowCoreThreadTimeOut后,核心线程也会超时回收。
4)unit:时间单位
配合keepAliveTime使用,支持毫秒、秒、分钟、小时等时间单位。
5)workQueue:任务阻塞队列
用于存储等待执行的任务,必须是阻塞队列。当所有核心线程都在忙碌时,新任务进入队列排队。
常用队列:
ArrayBlockingQueue:有界队列,固定容量,生产最常用,可控性强;
LinkedBlockingQueue:无界/有界队列,不设置容量则无限扩容,容易OOM;
SynchronousQueue:同步队列,不存储任务,直接转交线程执行;
DelayQueue:延迟队列,定时任务场景专用。
6)threadFactory:线程工厂
用于统一创建线程,可自定义线程名称、优先级、守护线程状态。
生产必备:必须自定义线程工厂,设置业务线程名称,线上问题排查日志时可快速定位线程归属业务,默认工厂线程名称无意义,无法排查问题。
7)handler:拒绝策略
当线程数达到最大值 + 任务队列已满,新任务触发拒绝策略,用于流量过载保护。JDK内置4种策略,同时支持自定义业务策略,后文重点落地讲解。
3.3 核心参数面试高频总结
任务执行优先级:核心线程 → 任务队列 → 非核心线程 → 拒绝策略
这是线程池源码最核心的执行顺序,所有源码逻辑均围绕该优先级展开。
四、ThreadPoolExecutor核心源码执行流程
4.1 线程池核心状态(源码常量)
ThreadPoolExecutor通过一个原子整型变量ctl,同时存储线程池状态和当前线程数量,高并发下保证原子性。
// 线程池运行状态privatestaticfinalintRUNNING=-1<<29;// 运行中,接收新任务privatestaticfinalintSHUTDOWN=0<<29;// 关闭,不接收新任务,执行存量任务privatestaticfinalintSTOP=1<<29;// 停止,不接收任务,中断正在执行任务privatestaticfinalintTIDYING=2<<29;// 整理中,所有任务终止privatestaticfinalintTERMINATED=3<<29;// 彻底终止状态流转:RUNNING → SHUTDOWN → STOP → TIDYING → TERMINATED
4.2 execute()核心方法源码流程
线程池提交任务核心方法execute(Runnable command),整体逻辑分为三步,完美对应参数优先级规则。
步骤1:判断核心线程数
当前工作线程数 < 核心线程数,直接新建核心线程执行任务。
步骤2:核心线程已满,入队排队
核心线程已满,尝试将任务加入阻塞队列。入队成功,任务等待执行;入队失败(队列已满),进入第三步。
步骤3:队列已满,创建非核心线程
队列已满,当前总线程数 < 最大线程数,新建非核心线程执行任务。
步骤4:触发拒绝策略
总线程数已达最大值、队列已满,新任务执行拒绝策略。
4.3 源码核心逻辑精简总结
很多人面试答不出线程池流程,记住一句万能口诀:先核心、再排队、再扩容、最后拒绝。
五、四种内置拒绝策略源码+适用场景
拒绝策略是线程池的过载保护机制,高并发流量洪峰下,防止服务无限堆积任务导致雪崩。JDK内置4种拒绝策略,每一种适配不同业务场景,禁止乱用。
5.1 AbortPolicy(默认策略)—— 直接抛出异常
源码逻辑:直接抛出RejectedExecutionException异常,中断任务提交。
适用场景:核心业务、不允许静默失败、需要异常告警的场景。例如订单支付、核心交易流程,任务失败必须感知并告警。
缺点:非核心业务大量抛异常会造成日志刷屏、接口报错率飙升。
5.2 CallerRunsPolicy(调用者运行策略)—— 退化为同步执行
源码逻辑:线程池满了之后,新任务由提交任务的主线程自行执行,不丢弃、不报错。
适用场景:不允许丢失任务、流量可控、非高吞吐场景。例如数据同步、文件处理、离线任务。
优点:无任务丢失;缺点:会阻塞主线程,降低接口吞吐量。
5.3 DiscardPolicy(丢弃策略)—— 静默丢弃任务
源码逻辑:线程池已满,直接丢弃新任务,不报错、不告警、无日志。
适用场景:非核心、可丢弃、时效性极强的任务。例如实时日志上报、心跳检测、非关键统计数据。
致命缺点:问题无法排查,生产慎用,极易隐藏线上故障。
5.4 DiscardOldestPolicy(丢弃最旧任务策略)
源码逻辑:丢弃队列中最旧的未执行任务,腾出位置执行当前新任务。
适用场景:时效性优先、新任务覆盖旧任务的场景。例如实时监控、实时数据刷新、定时推送。
缺点:会丢失历史任务,不适合有序、不可丢失的业务。
六、生产级自定义拒绝策略(业务核心落地)
JDK内置拒绝策略无法适配复杂业务,生产环境90%场景需要自定义拒绝策略,实现:任务拒绝告警、日志记录、重试机制、降级兜底。
6.1 自定义拒绝策略实战代码
importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;importjava.util.concurrent.RejectedExecutionException;importjava.util.concurrent.RejectedExecutionHandler;importjava.util.concurrent.ThreadPoolExecutor;/** * 生产级自定义线程池拒绝策略 * 核心能力:日志记录 + 告警标记 + 友好异常提示 */publicclassCustomRejectPolicyimplementsRejectedExecutionHandler{privatestaticfinalLoggerlog=LoggerFactory.getLogger(CustomRejectPolicy.class);@OverridepublicvoidrejectedExecution(Runnabler,ThreadPoolExecutorexecutor){// 1. 记录拒绝任务的详细日志,用于线上问题排查log.error("【线程池任务拒绝】线程池已满,任务被拒绝!"+"核心线程数:{},最大线程数:{},当前线程数:{},队列容量:{},队列剩余:{}",executor.getCorePoolSize(),executor.getMaximumPoolSize(),executor.getPoolSize(),executor.getQueue().capacity(),executor.getQueue().remainingCapacity());// 2. 可扩展:接入告警系统(钉钉/企业微信/邮件告警)// alertService.sendAlert("线程池过载,任务拒绝,服务存在压力!");// 3. 抛出业务自定义异常,替代原生异常,方便全局捕获降级thrownewRejectedExecutionException("系统繁忙,当前任务排队过载,请稍后重试!");}}6.2 自定义策略业务价值
解决默认策略无日志、无告警、无法排查问题的痛点;
统一业务异常提示,提升用户体验;
可扩展重试、降级、兜底存储逻辑,保证业务可靠性。
七、生产级线程池完整配置方案(可直接复用)
结合前文参数、队列、拒绝策略,给出一套线上可直接落地的通用线程池配置,适配绝大多数业务场景。
importjava.util.concurrent.*;/** * 生产通用业务线程池 * 适配:普通业务异步处理、接口解耦、非阻塞任务 */publicclassBusinessThreadPool{// 核心线程数:根据业务微调,常规业务设为5-10privatestaticfinalintCORE_POOL_SIZE=8;// 最大线程数privatestaticfinalintMAX_POOL_SIZE=20;// 空闲线程超时时间privatestaticfinallongKEEP_ALIVE_TIME=30L;// 队列容量:有界队列,防止OOMprivatestaticfinalintQUEUE_CAPACITY=100;// 全局单例线程池publicstaticfinalThreadPoolExecutorBUSINESS_POOL=newThreadPoolExecutor(CORE_POOL_SIZE,MAX_POOL_SIZE,KEEP_ALIVE_TIME,TimeUnit.SECONDS,newArrayBlockingQueue<>(QUEUE_CAPACITY),// 自定义线程工厂,命名规范,便于排查r->{Threadthread=newThread(r,"business-pool-thread-"+r.hashCode());thread.setDaemon(false);returnthread;},// 自定义业务拒绝策略newCustomRejectPolicy());// 静态关闭钩子,项目关闭时优雅销毁线程池static{Runtime.getRuntime().addShutdownHook(newThread(()->{BUSINESS_POOL.shutdown();try{if(!BUSINESS_POOL.awaitTermination(3,TimeUnit.SECONDS)){BUSINESS_POOL.shutdownNow();}}catch(InterruptedExceptione){BUSINESS_POOL.shutdownNow();}}));}}八、线程池参数调优核心公式(面试+架构必备)
线程池参数没有固定标准答案,但有通用调优公式,根据任务类型区分配置。
8.1 CPU密集型任务
任务大量占用CPU,无阻塞、无IO等待。
公式:最大线程数 = CPU核心数 + 1
原理:CPU密集任务线程过多会导致上下文切换频繁,降低效率,少量线程效率最高。
8.2 IO密集型任务(业务主流)
数据库查询、Redis请求、接口调用、文件读写等阻塞任务。
公式:最大线程数 = CPU核心数 * 2或CPU核心数 / (1 - 阻塞系数)
原理:IO阻塞时线程空闲,多创建线程可充分利用CPU,提升并发吞吐量。
九、高频面试题+生产避坑总结
9.1 经典面试问答
问:线程池提交任务的完整执行流程?
答:核心线程未满创建核心线程 → 核心线程满任务入队 → 队列满创建非核心线程 → 线程数达上限触发拒绝策略。问:核心线程和非核心线程区别?
答:核心线程常驻不回收,非核心线程空闲超时自动回收;核心线程是基础运力,非核心线程是突发扩容运力。问:为什么禁止Executors创建线程池?
答:FixedThreadPool无界队列可能OOM;CachedThreadPool线程无限创建,耗尽系统线程资源。问:拒绝策略什么时候触发?
答:必须同时满足两个条件:任务队列已满 + 当前线程数达到最大线程数。
9.2 生产避坑核心要点
必须使用有界队列,无界队列必炸OOM;
必须自定义线程工厂,规范线程名称,便于线上排查;
核心业务禁止使用DiscardPolicy静默丢弃;
项目关闭必须优雅关闭线程池,防止任务丢失;
不同业务隔离线程池,避免单一业务拖垮全局线程池。
十、全文总结
本文完整深挖了ThreadPoolExecutor底层源码,从线程池设计思想、七大核心参数、任务执行源码流程,到四种拒绝策略原理、场景适配、自定义业务落地,最后给出生产级配置模板和参数调优公式,形成理论+源码+实战+调优+避坑的完整闭环。
线程池是Java并发体系的重中之重,也是面试和架构设计的核心考点,生产中只要做好参数合理配置、队列有界、拒绝策略适配、线程隔离、优雅关闭,即可规避99%的线程池线上问题。
下一篇JDK系列11:全新特性详解,本地变量var、HTTP Client、废弃API梳理
💖 点赞+收藏+关注,持续更新JDK核心进阶系列,告别面试短板,夯实架构基础!