Skip to content

Java线程池的工作流程

第一部分:线程池的核心参数

ThreadPoolExecutor 最完整的构造函数为例,它有7个核心参数:

java
public ThreadPoolExecutor(
    int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue,
    ThreadFactory threadFactory,
    RejectedExecutionHandler handler)

下面我们逐一解释:

1. corePoolSize - 核心线程数

  • 定义:线程池中长期存活的、即使处于空闲状态也不会被销毁的线程数量(除非设置了 allowCoreThreadTimeOut 为 true)。
  • 作用:它们是线程池的基本“劳动力”。
  • 工作流程:当有新任务提交时,线程池会优先创建核心线程来处理任务,即使有其他核心线程空闲也会创建,直到达到 corePoolSize

2. maximumPoolSize - 最大线程数

  • 定义:线程池允许创建的最大线程数量。
  • 作用:这是线程池的“劳动力”上限。
  • 工作流程:当核心线程都在忙,并且工作队列也已经排满时,线程池才会创建新的临时线程(非核心线程)来处理任务,但总线程数不能超过 maximumPoolSize

3. keepAliveTime + unit - 线程存活时间

  • 定义:当线程数超过 corePoolSize 时,那些多余的“临时”线程在空闲状态下等待新任务的最长时间。如果超过这个时间还没有新任务,它们就会被销毁。
  • 单位unit 参数是 keepAliveTime 的时间单位(如 TimeUnit.SECONDS)。
  • 作用:为了节省系统资源,避免在低负载时维持过多线程。

4. workQueue - 工作队列

  • 定义:一个用于存放待处理任务的阻塞队列(BlockingQueue)。
  • 作用:在任务激增时,起到缓冲和排队的作用。
  • 常用队列类型
    • LinkedBlockingQueue(无界队列):任务可以无限堆积,会导致 maximumPoolSize 参数失效,永远不会创建超过 corePoolSize 的线程。可能引起内存溢出。
    • ArrayBlockingQueue(有界队列):队列大小固定。这是最常用的场景,可以配合 maximumPoolSize 一起防止资源耗尽。
    • SynchronousQueue(同步移交队列):它不存储元素。每个插入操作必须等待另一个线程的移除操作。因此,如果没有立即可用的线程来执行任务,就会创建新线程(如果没达到最大线程数),否则任务会被拒绝。这要求线程池的 maximumPoolSize 足够大。

5. threadFactory - 线程工厂

  • 定义:用于创建新线程的工厂。
  • 作用:可以自定义线程的名称、优先级、是否为守护线程等,方便问题排查和监控。如果不指定,会使用默认的 Executors.defaultThreadFactory()

6. handler - 拒绝策略

  • 定义:当线程池已经关闭,或者线程池和工作队列都已达到饱和状态(即所有线程都在忙且队列已满)时,对新提交的任务采取的处理策略。
  • 作用:这是系统的“安全阀”,防止任务无限堆积导致系统崩溃。

第二部分:线程池的工作流程(参数如何协同工作)

理解参数后,我们再通过一个流程图来看它们是如何配合的:

mermaid
flowchart TD
    A[提交新任务] --> B{核心线程<br>是否已满?<br>(< corePoolSize)}
    B -- 否 --> C[创建新的核心线程执行任务]
    B -- 是 --> D{工作队列<br>是否已满?}
    D -- 否 --> E[将任务放入工作队列]
    D -- 是 --> F{线程总数<br>是否已达上限?<br>(< maximumPoolSize)}
    F -- 否 --> G[创建新的临时线程执行任务]
    F -- 是 --> H[执行拒绝策略]
  1. 任务提交。
  2. 如果当前运行的线程数 小于 corePoolSize,则创建新线程(核心线程)来执行任务。
  3. 如果当前运行的线程数 大于等于 corePoolSize,则尝试将任务放入工作队列 (workQueue)。
  4. 如果工作队列已满,则尝试创建新的临时线程(非核心线程)来执行任务,直到线程数达到 maximumPoolSize
  5. 如果线程数也已达到 maximumPoolSize,并且队列也满了,则触发拒绝策略 (handler)。

第三部分:拒绝策略 (RejectedExecutionHandler)

JDK 内置了四种常见的拒绝策略,它们都实现了 RejectedExecutionHandler 接口。

1. AbortPolicy - 中止策略(默认)

  • 行为:直接抛出 RejectedExecutionException 异常。
  • 优点:明确地通知调用者任务被拒绝了,不会无声无息地丢失任务。
  • 缺点:调用者需要捕获并处理异常。
  • 适用场景大多数关键业务场景,需要及时感知到系统过载。

2. CallerRunsPolicy - 调用者运行策略

  • 行为:不抛弃任务,也不抛异常,而是将某些被拒绝的任务回退给调用者线程来执行。
  • 优点
    • 不会丢失任务。
    • 提交任务的线程(调用者)需要自己去执行任务,这会占用调用者的时间,从而减缓了新任务提交的速度,相当于一种负反馈机制,给了线程池一些喘息的时间。
  • 适用场景:不允许任务丢失,且能承受任务执行速度变慢的场景。例如,可以在Web服务器中防止队列过载时,将压力回传到数据源(如数据库连接池)。

3. DiscardPolicy - 丢弃策略

  • 行为:默默地将无法处理的任务丢弃,不做任何通知
  • 优点:实现简单。
  • 缺点任务无声无息地丢失了,很难发现问题。
  • 适用场景不推荐使用,除非你非常确定可以丢弃某些不重要的任务(如日志记录、心跳检测等)。

4. DiscardOldestPolicy - 丢弃最老策略

  • 行为:丢弃工作队列中队首(即下一个将要被执行)的旧任务,然后尝试重新提交当前这个新任务。
  • 优点:给了新任务一次机会。
  • 缺点:可能会丢弃非常重要的老任务。
  • 适用场景:后到来的任务优先级更高的场景。例如,一个实时消息系统,新的消息比旧的消息更有价值。

总结与建议

参数说明设置建议
corePoolSize常驻核心线程数根据任务类型(CPU密集型/IO密集型)和机器配置来定。
maximumPoolSize最大线程数设置一个上限,防止资源耗尽。通常和 corePoolSize 及队列容量一起权衡。
workQueue任务队列推荐使用有界队列(如 ArrayBlockingQueue),配合拒绝策略,避免资源耗尽。
keepAliveTime线程空闲时间对于突发流量的系统,可以设置得短一些,以便及时回收资源。
handler拒绝策略生产环境强烈建议使用 AbortPolicyCallerRunsPolicy,避免使用默认的或丢弃策略,以便及时发现问题。

最佳实践

  • 不要使用 Executors 的快捷方法(如 newFixedThreadPool, newCachedThreadPool),因为它们使用无界队列或无限最大线程数,容易导致OOM。
  • 应该根据实际情况,手动创建 ThreadPoolExecutor,并为其指定合适的参数,特别是使用有界队列明确的拒绝策略
  • 对于需要记录被拒绝任务的场景,可以自定义拒绝策略,在拒绝任务前记录日志。
/src/technology/dateblog/2025/10/20251028-java%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%9A%84%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B.html