线程池解析

线程池是一种常用的并发设计模式,其核心思想是预先创建一组线程来执行任务,以减少频繁创建和销毁线程带来的开销,同时更好地控制并发数、提升资源利用率。下面详细介绍线程池的设计思路、工作原理及工作流程。

一、线程池的核心组成部分

任务队列

作用​​:存放待执行的任务,是一个并发安全的数据结构
​​类型​​:

  • 无界队列​​(如LinkedBlockingQueue):任务无限增长,可能导致OOM
  • 有界队列​​(如ArrayBlockingQueue):设置队列容量,超过且线程数达到最大线程数是触发拒绝策略

工作线程

线程池中预先创建或动态创建的一组线程,每个线程不断从任务队列中获取任务并执行。任务执行完毕后,线程不会销毁,而是返回到线程池中等待下一个任务,从而实现线程的复用。

线程池管理器

负责线程池的生命周期管理,包括线程的创建、销毁、扩容、收缩以及线程状态的监控。它会根据任务负载、核心线程数和最大线程数等参数动态调整线程数量。

任务调度和拒绝策略

  • 任务调度: 当任务提交时,线程池会根据当前线程数和队列状态决定是直接创建新线程执行任务,还是将任务加入队列等待空闲线程处理。

  • 拒绝策略: 当任务队列已满且线程数达到上限时,线程池需要决定如何处理新提交的任务(例如:抛出异常、丢弃任务或采用其他策略)。

线程池的工作流程

  1. 任务提交(Submit)
    用户通过接口提交一个任务,例如:
    pool.submit([]() {
        std::cout << "执行任务\n";
    });
    
  • 此任务被包装为一个可执行的单元(std::function<void()>Runnable 等)。
  • 提交逻辑负责判断线程池当前状态。
  1. 判断线程和队列状态
    2.1 当前线程数 < 核心线程数?
    👉 是 → 立即创建线程 来处理任务,不进队列
    👉 否 → 进入 2.2

    2.2 当前线程数 ≥ 核心线程数,尝试将任务放入队列
    如果任务队列有空位 → 将任务放入队列,等待空闲线程取任务处理。
    否则 → 进入 2.3

    2.3 任务队列已满,线程数 < 最大线程数?
    👉 是 → 扩容:再创建一个线程,立即执行任务
    👉 否 → 进入 2.4

    2.4 线程数已达最大值,且队列也满了
    👉 启用 拒绝策略,处理方式通常有:

    • 抛出异常(如 Java 中的 RejectedExecutionException)
    • 丢弃任务
    • 让主线程自己执行任务(降低系统压力)
  2. 任务执行
    线程从队列中取出任务:

    while (true) {
        if (!task_queue.empty()) {
            auto task = task_queue.front();
            task_queue.pop();
            task(); // 执行任务
        }
    }
    
    • 每个工作线程在一个无限循环中,不断尝试从任务队列取任务。

    • 任务执行完不会销毁线程,而是 等待下一个任务(即线程复用)。

  3. 线程复用:返回线程池继续等待
    任务执行完后,线程不会退出:

    while (true) {
        wait for task;
        if (task received) {
            execute;
        }
    }
    
    • 避免频繁的 new thread + destroy 的资源浪费。
    • 达到线程重用的目的。
  4. 线程回收(空闲超时机制)
    当任务处理完,且队列为空,线程就处于“空闲状态”:

    • 如果线程池中的线程数量 > 核心线程数,
    • 且线程空闲超过设定时间(如 60 秒),
    • 则该线程会自动退出,节省资源。
    if (current_thread > core && idle_time > timeout) {
        exit thread;
    }
    
  5. 关闭线程池(Shutdown)

    6.1 优雅关闭(Graceful Shutdown)

    • 不再接受新任务。
    • 等待当前队列和正在执行的任务完成。
    • 然后退出所有线程。

    6.2 强制关闭(Force Shutdown)

    • 尝试中断正在执行的任务。
    • 丢弃队列中的任务。
    • 立即停止所有线程(风险:任务丢失或资源泄漏)

线程池核心参数

核心线程数

定义:
核心线程数是线程池始终保持存活的线程数量,即使这些线程当前没有任务可执行,它们也不会被销毁。

作用:

  • 线程池在接收到任务时,优先使用核心线程来执行任务,而不会直接创建新的非核心线程。

  • 只有当核心线程数已满(所有核心线程都在执行任务),且任务队列也已满,线程池才会创建额外的线程(但不能超过最大线程数)。

  • 核心线程数决定了线程池的基本并发能力

配置建议:

  • CPU 密集型任务(如计算任务):一般设置为 CPU 核心数,避免线程上下文切换影响性能。例如,8 核 CPU 可设置 corePoolSize = 8

  • I/O 密集型任务(如网络、数据库操作):通常设置为 CPU 核心数 × 2 或更多,例如 corePoolSize = 16(因为 I/O 操作大部分时间处于等待状态,可以利用更多线程提高吞吐量)。

  • why? 为了提高CPU利用率,我们希望处于runnable状态的线程数约等于CPU核心数:核心线程数 * 线程处于runnable状态的概率 = CPU核心数。一般而言,CPU密集型任务的 线程处于runnable状态的概率约为1,所以核心线程数 = CPU核心数;IO密集型任务的 线程处于runnable状态的概率约50%(不一定,具体情况具体分析),所以核心线程数 = 2 * CPU核心数

最大线程数

定义:
最大线程数是线程池允许创建的最大线程数,当任务队列已满且核心线程都在工作时,线程池会创建新线程直到达到最大线程数

作用:

  • 限制线程池中的并发线程数量,避免线程数过多导致资源耗尽(如内存、CPU 争用)。

  • 线程池只有在任务队列已满时才会超过核心线程数,创建新线程执行任务。

  • 超过最大线程数时,新任务会触发拒绝策略(详见后文)。

配置建议:

  • CPU 密集型任务:一般与 corePoolSize 相同或略多一些(如 maxPoolSize = corePoolSize + 2)。

  • I/O 密集型任务:一般比 corePoolSize 多一些,比如 maxPoolSize = corePoolSize × 2,以利用 I/O 等待时间执行更多任务。

任务队列的大小

定义:
任务队列是存储等待被执行的任务的队列。当核心线程都在忙时,新的任务会被放入任务队列中等待空闲线程执行

作用:

  • 缓冲任务,避免频繁创建和销毁线程,提升性能。

  • 影响线程池的扩容策略

    • 如果队列未满,任务会优先进入队列,而不会创建新线程。

    • 只有当队列已满且核心线程都在忙时,线程池才会创建新线程。

    • 任务队列过大可能导致任务堆积,过小可能导致频繁扩容线程数。

常见队列类型及影响:

队列类型特性适用场景
有界队列(如 ArrayBlockingQueue限制最大任务数,避免OOM任务执行时间长,需控制负载
无界队列(如 LinkedBlockingQueue任务无限制增长,可能导致OOM任务执行快,吞吐量高
优先级队列(如 PriorityBlockingQueue按优先级执行任务需要按任务重要性排序

配置建议:

  • 如果任务队列很大(如无界队列):线程池不会扩展到最大线程数,而是让任务排队。

  • 如果任务队列较小(如有界队列):线程池更容易扩展线程数来执行任务,提高吞吐量,但可能导致更多线程竞争 CPU。

线程空闲超时时间

定义:
当线程数超过核心线程数,且这些额外的线程在 keepAliveTime 时间内没有新任务可执行,那么这些线程就会被销毁。

作用:

  • 控制线程回收:线程池中的非核心线程(即超过 corePoolSize 的线程)在空闲 keepAliveTime 后会被销毁,以释放资源。

  • 线程复用:如果任务到来间隔较短,线程不会马上销毁,从而提高性能。

默认行为:

  • 默认情况下,只有非核心线程会受 keepAliveTime 影响。

  • 但如果调用 allowCoreThreadTimeOut(true)核心线程在空闲 keepAliveTime 后也会被销毁。

配置建议:

  • 短时间高并发任务(如秒杀、批量任务处理)keepAliveTime 可以设短一些,快速回收多余线程,避免资源浪费。

  • 长期运行任务(如 Web 服务器):可以设长一点,避免线程频繁销毁和创建,提高线程复用率。

拒接策略

定义:
当任务队列已满,并且线程池中的线程数已达到最大线程数,此时如果再提交任务,线程池会根据拒绝策略来决定如何处理这些任务。

常见拒绝策略:

拒绝策略作用适用场景
AbortPolicy(默认策略)直接抛出 RejectedExecutionException 异常任务不能丢失,需手动处理异常
DiscardPolicy直接丢弃新任务,不做任何处理对丢失任务不敏感的场景
DiscardOldestPolicy丢弃队列中最老的任务,然后再尝试提交新任务优先处理最新任务,适合实时性要求高的系统
CallerRunsPolicy调用者线程(提交任务的线程)直接执行任务适用于限制任务增长,防止任务丢失

配置建议:

  • 高可靠性系统(不能丢任务):建议使用 CallerRunsPolicy,让提交任务的线程执行任务,降低线程池压力。

  • 任务可丢弃系统(如日志收集):可以使用 DiscardPolicy,减少线程池负担。

  • 实时性要求高的系统(如交易系统):可以使用 DiscardOldestPolicy,确保最新任务优先处理。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值