基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

当一个新的任务提交到线程池时,线程池完整的执行策略和处理流程是怎样的?

知识点图片

在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 种默认处理方式:

  1. AbortPolicy(默认):直接抛出 RejectedExecutionException 异常,阻止系统正常运行。
  2. CallerRunsPolicy:由调用 execute 方法的那个线程(通常是主线程)来自己执行这个任务。这会降低新任务的提交速度,起到限流的作用。
  3. DiscardPolicy:默默地丢弃掉这个新任务,什么都不做,也不抛异常。
  4. DiscardOldestPolicy:把队列头部(最老)的那个任务丢弃,然后重新尝试把新任务加入队列。
00:00
00:00