Java 线程池详解

Java 线程池(Thread Pool)详解

在 Java 中,线程池(Thread Pool) 是用于管理和复用线程的资源池
相比于手动 new Thread(),线程池具有以下优势:

  • 显著减少线程创建 / 销毁的性能开销
  • 有效控制系统中的 In-flight(在途)任务数量
  • 防止线程无限增长导致系统失控

Java 通过 java.util.concurrent(JUC) 包中的
ThreadPoolExecutor 提供最核心、最底层的线程池实现。

⚠️ 阿里巴巴 Java 开发手册明确规定:必须显式使用 ThreadPoolExecutor,禁止直接使用 Executors 工具类。


一、ThreadPoolExecutor 构造方法(七大核心参数)

理解线程池,本质就是理解这 7 个参数,它们共同决定了线程池的“性格”。

public ThreadPoolExecutor(
    int corePoolSize,                  // 1. 核心线程数
    int maximumPoolSize,               // 2. 最大线程数
    long keepAliveTime,                // 3. 非核心线程空闲存活时间
    TimeUnit unit,                     // 4. 时间单位
    BlockingQueue<Runnable> workQueue, // 5. 任务阻塞队列
    ThreadFactory threadFactory,        // 6. 线程工厂(建议自定义,便于定位问题)
    RejectedExecutionHandler handler    // 7. 拒绝策略
)

二、任务处理流程(重点)

当一个新任务通过 execute() 提交时,线程池严格遵循以下 “三阶一拒绝” 逻辑:

1 核心期(Core Phase)

当前线程数 < corePoolSize

即使有空闲线程,也会创建新线程执行任务

2 排队期(Queue Phase)

核心线程已满

任务被放入 workQueue 阻塞队列中等待

3 爆发期(Max Phase)

队列已满

当前线程数 < maximumPoolSize

创建 非核心线程 紧急处理任务

4 拒绝期(Reject Phase)

线程数已达 maximumPoolSize

队列也已满

触发 拒绝策略(RejectedExecutionHandler)

三 阻塞队列 (workQueue)

队列决定了线程池如何“缓冲”任务:

  • ArrayBlockingQueue:有界队列,防止 OOM(内存溢出)。
  • LinkedBlockingQueue:常用,默认是无界的(小心 OOM),也可指定容量。
  • SynchronousQueue不存储任务的队列。每个插入操作必须等待一个获取操作,适合高吞吐量的直接移交(如 CachedThreadPool)。

如何确定 corePoolSize?通常根据任务性质分为两类:

  • CPU 密集型(计算、加密、压缩):
    • 核心线程数 = CPU 核数 + 1
    • 目的:尽量减少线程切换带来的损耗。
  • IO 密集型(数据库操作、网络调用、文件读写):
    • 核心线程数 = CPU 核数 * 2 或更高。
    • 公式参考:CPU 核数 * (1 + 线程平均等待时间 / 平均工作时间)
    • 目的:IO 阻塞时,让其他线程利用 CPU。

四 如何配置参数及线程池状态监控

1.如何确定 corePoolSize?通常根据任务性质分为两类:

  • CPU 密集型(计算、加密、压缩):

    • 核心线程数 = CPU 核数 + 1
    • 目的:尽量减少线程切换带来的损耗。
  • IO 密集型(数据库操作、网络调用、文件读写):

    • 核心线程数 = CPU 核数 * 2 或更高。

    • 公式参考:CPU 核数 * (1 + 线程平均等待时间 / 平均工作时间)

    • 目的:IO 阻塞时,让其他线程利用 CPU。

2.线程池状态监控

你可以通过以下方法实时监控线程池状态(对应你之前提到的 In-flight 概念):

  • getPoolSize():当前活跃线程总数。
  • getActiveCount():正在执行任务的线程数(真正的 In-flight)。
  • getQueue().size():在队列中排队的任务数。
  • getCompletedTaskCount():已完成的任务总数

思考:三阶一拒绝中如何让线程创建变成 核心线程》空闲线程》任务队列

方案:重写队列的 offer 方法

这是 Tomcat 和 Dubbo 采用的经典做法。核心思路是:在调用队列的 offer 方法时,欺骗线程池说“我已满了”,迫使线程池去创建非核心线程。

  1. 自定义一个队列继承 LinkedBlockingQueue
  2. ThreadPoolExecutor 的引用注入到队列中。
  3. 重写 offer 方法:如果当前线程数 < 最大线程数,返回 false(模拟队列已满)。
public class PriorityQueue extends LinkedBlockingQueue<Runnable> {
    private ThreadPoolExecutor executor;

    public void setExecutor(ThreadPoolExecutor executor) {
        this.executor = executor;
    }

    @Override
    public boolean offer(Runnable r) {
        int currentThreads = executor.getPoolSize();
        // 如果还有空间创建非核心线程,直接返回 false,迫使线程池创建线程
        if (currentThreads < executor.getMaximumPoolSize()) {
            return false;
        }
        // 如果已经达到最大线程数,才真正入队
        return super.offer(r);
    }
}

关键点: 这种方案下,你需要配合一个拒绝策略。因为当 offer 返回 false 且线程数达到 max 时,线程池会调用 RejectedExecutionHandler。在拒绝策略里,你需要尝试再次将任务塞进队列

posted @ 2026-01-16 10:02  水星逃逸  阅读(3)  评论(0)    收藏  举报