当一个新的任务提交到线程池时,线程池完整的执行策略和处理流程是怎样的?
在Java中(以标准的 ThreadPoolExecutor 为例),当一个新的任务提交到线程池时,线程池有一套非常严谨且高效的执行策略和处理流程。
我们可以将整个处理流程总结为 “四个阶段” 和 “三道防线”。以下是完整的执行策略和详细处理流程:
一、 核心处理流程(四步走)
当调用 execute() 或 submit() 提交一个新任务时,线程池会按照以下顺序进行判断和处理:
第一步:判断核心线程池(Core Pool)是否已满
- 逻辑:当前运行的线程数是否小于
corePoolSize(核心线程数)? - 是:即使当前有空闲的核心线程,也会强制创建一个新的核心线程来执行这个新任务。
- 否:核心线程数已达上限,进入第二步。
第二步:判断工作队列(Work Queue)是否已满
- 逻辑:如果核心线程已满,线程池会尝试将新任务放入
workQueue(阻塞队列)中等待。 - 是(放入成功):任务将在队列中等待,直到有核心线程空闲下来主动去队列里抓取它执行。
- 否(队列已满):比如使用的是有界队列(如
ArrayBlockingQueue)且队列已塞满,进入第三步。
第三步:判断最大线程池(Maximum Pool)是否已满
- 逻辑:如果工作队列也满了,线程池会检查当前运行的线程数是否小于
maximumPoolSize(最大线程数)。 - 是:创建一个新的非核心线程(救急线程),并直接运行这个新提交的任务(注意:不是从队列里拿老任务,而是直接处理刚被拒之门外的新任务)。
- 否:当前线程数已经达到了最大线程数,且队列也满了,进入第四步。
第四步:执行拒绝策略(Rejection Policy)
- 逻辑:线程池已经“弹尽粮绝”(线程达到最大,队列塞满),无法再处理任何新任务,此时将触发
RejectedExecutionHandler的拒绝策略。
二、 流程图解
为了更直观地理解,可以参考以下流程图:
plaintext
任务提交
│
▼
1. 当前线程数 < corePoolSize ?
├── [是] ─> 创建【核心线程】执行任务
│
└── [否]
▼
2. 阻塞队列 workQueue 满了 ?
├── [否] ─> 将任务放入【阻塞队列】排队等待
│
└── [是]
▼
3. 当前线程数 < maximumPoolSize ?
├── [是] ─> 创建【非核心线程】立即执行该任务
│
└── [否]
▼
4. 触发【拒绝策略】 (RejectedExecutionHandler)
三、 线程池运行过程中的“潜规则”与细节
除了上述的基础提交流程,线程池在实际运行中还有几个极其重要的底层逻辑:
1. 线程是如何消费任务的?(线程生命周期)
- 任务执行完毕后:无论核心线程还是非核心线程,执行完当前任务后都不会立刻死亡,而是会进入一个循环,不断从
workQueue中调用take()或poll()方法获取下一个任务。 - 空闲回收机制:
- 如果队列为空,线程会阻塞等待。
- 如果当前线程数 >
corePoolSize,那么这些多出来的非核心线程在空闲等待了keepAliveTime(存活时间)之后,如果还没等到新任务,就会被销毁回收。 - 核心线程默认不会超时回收(会一直阻塞等待),但如果设置了
allowCoreThreadTimeOut(true),核心线程空闲超时也会被销毁。
2. 一个常见的认知误区(面试常考)
- 误区:当队列满了,创建非核心线程时,非核心线程会去队列头取最早的任务执行,把新任务放入队列。
- 真相:非核心线程会直接执行刚刚提交的、导致队列溢出的那个新任务。队列里排队的老任务依然在排队。这在某种程度上是不公平的(后提交的任务反而先执行了)。
3. 阻塞队列的类型对流程的决定性影响
不同队列会导致线程池行为大相径庭:
LinkedBlockingQueue(无界队列):如果未指定容量,它是无界的。这意味着第二步永远不会满,所以第三步(创建非核心线程)和第四步(拒绝策略)永远不会触发。最大线程数maximumPoolSize的设置在此刻形同虚设。SynchronousQueue(同步队列):它没有任何容量(内部不存元素)。任务一旦提交,如果核心线程满了,第二步必定失败,会直接跳到第三步创建非核心线程。CachedThreadPool就是用的这个队列。ArrayBlockingQueue(有界队列):严格按照上述完整的四步流程走。
4. 四种标准拒绝策略
当走到第四步时,Java 提供了 4 种默认处理方式:
AbortPolicy(默认):直接抛出RejectedExecutionException异常,阻止系统正常运行。CallerRunsPolicy:由调用execute方法的那个线程(通常是主线程)来自己执行这个任务。这会降低新任务的提交速度,起到限流的作用。DiscardPolicy:默默地丢弃掉这个新任务,什么都不做,也不抛异常。DiscardOldestPolicy:把队列头部(最老)的那个任务丢弃,然后重新尝试把新任务加入队列。