在Java并发编程中,线程池是我们提升程序性能、管理系统资源的利器。它避免了频繁创建和销毁线程带来的开销。但要想用好线程池,深入理解其底层的配置参数和执行逻辑是必修课。今天,我们就来扒一扒线程池执行器的底裤,看看一个任务丢进线程池后,到底经历了什么。
1. 掌控全局:线程池的七大核心参数
要理解线程池的执行流程,首先得认识它的“七个武器”。在Java中,真正实现线程池的核心类是线程池执行器,它的全参构造函数包含了以下7个关键参数:
| 参数名 | 类型 | 核心作用 |
|---|---|---|
corePoolSize | int | 核心线程数。即使线程处于空闲状态,也不会被销毁(除非设置了允许核心线程超时)。 |
maximumPoolSize | int | 最大线程数。线程池允许创建的最大线程数量。 |
keepAliveTime | long | 空闲线程存活时间。当线程数大于核心线程数时,多余的空闲线程在这个时间内没活干就会被销毁。 |
unit | TimeUnit | 保持存活时间的时间单位(如秒、毫秒等)。 |
workQueue | BlockingQueue | 任务阻塞队列。用于存放被提交但尚未被执行的任务。 |
threadFactory | ThreadFactory | 线程工厂。用于定制线程的创建过程(如设置有意义的线程名称,方便排查问题)。 |
handler | RejectedExecutionHandler | 拒绝策略。当队列满了,且线程数达到最大限制时,对新任务的处理方式。 |
2. 抽丝剥茧:线程池的执行流程详解
当一个新任务通过执行()或提交()方法提交到线程池时,它的命运将由上述参数共同决定。
整个调度流程可以总结为四个步骤、三道关卡:
-
第一关:核心线程(Core Pool) 当新任务提交时,线程池首先检查当前运行的线程数是否小于
核心池大小。- 是:立即创建一个新的核心线程来执行该任务(哪怕此时有其他空闲的核心线程)。
- 否:进入下一关。
-
第二关:任务队列(Work Queue) 如果核心线程池已满,线程池会尝试将任务塞进
工作队列阻塞队列中。- 成功:任务在队列中乖乖排队,等待空闲的核心线程来取用执行。
- 失败(队列已满) :进入下一关。
-
第三关:最大线程数(Maximum Pool) 如果队列也被塞满了,此时线程池会检查当前运行的线程数是否小于
最大泳池尺寸。- 是:立即创建非核心线程(也叫救急线程)来执行这个新提交的任务(注意:此时并非执行队列里等待最久的任务)。
- 否:所有资源都已耗尽,进入最后的保底环节。
-
终局:拒绝策略(Rejected Policy) 如果线程数已经达到了
最大泳池尺寸,且队列已满,线程池就会无情地触发处理程序拒绝策略。常见的策略有:中止策略(默认):直接抛出异常,阻止系统正常运行。CallerRunsPolicy:把任务交给调用者所在的线程去执行。丢弃策略:默默丢弃任务,不抛出异常。丢弃最旧策略:丢弃队列中最老的任务,然后尝试重新提交新任务。
核心口诀:先核心,后队列,再最大,最后拒绝。
3. 快捷创建:Executors 的三种常用线程池
为了方便开发者,Java的遗嘱执行人工具类提供了一些静态工厂方法,用于快速生成配置好的线程池。最常用的有以下三种:
1. 固定线程池(固定大小线程池)
- 特点:
核心池大小和最大泳池尺寸相等,全是核心线程,没有非核心线程。 - 队列:使用的是
链接阻塞队列(无界队列,容量为整数.MAX_VALUE)。 - 适用场景:已知并发压力的场景,适合执行长期的、平稳的任务,资源开销可控,也可以简单理解为设备性能一般情况下使用。
2. 缓存线程池(缓存线程池)
- 特点:
核心池大小为0,最大泳池尺寸为整数.MAX_VALUE(即2^31-1,相当于无限大)。非核心线程的空闲存活时间为60 秒。 - 队列:使用的是
同步队列(同步队列,本身不存储元素,直接将任务交付给线程)。 - 适用场景:适合处理大量短平快的任务。任务一来就立刻执行,执行完后线程保留60秒,随时准备复用,也就是说设备性能十分好使用。
3. SingleThreadExecutor(单线程池)
- 特点:
corePoolSize和maximumPoolSize都是 1,池子里永远只有一个线程在工作。 - 队列:使用的也是
LinkedBlockingQueue(无界队列,即2^31-1)。 - 适用场景:需要保证任务按照提交顺序串行执行的场景,不需要考虑线程同步问题。
避坑警告:为什么大厂禁止使用 Executors? 阿里巴巴《Java开发手册》中明确规定,不允许使用 Executors 去创建线程池,而是推荐通过 ThreadPoolExecutor 的方式。 原因在于 Executors 提供的方案存在严重的内存溢出(OOM)风险:
固定线程池和单线程执行器允许的请求队列长度为整数.MAX_VALUE,可能会堆积海量请求,导致OOM。缓存线程池允许创建的线程数量为整数.MAX_VALUE,可能会创建极多的线程,同样导致OOM。
最佳实践:老老实实使用线程池执行器构造函数,根据业务场景自己评估并设定合理的队列长度和最大线程数!