Java 线程池详解:参数、流程与实战

在 Java 并发编程中,线程池是绕不开的核心组件。它通过预先创建线程并复用,避免了频繁创建 / 销毁线程的性能开销,同时能有效控制并发线程数量,防止资源耗尽。今天我们就从核心参数工作流程实战用法三个维度,彻底搞懂线程池。

一、线程池的核心参数:ThreadPoolExecutor 的 7 个关键配置

Java 中所有线程池的底层实现都是 ThreadPoolExecutor(位于 java.util.concurrent 包),它的构造方法定义了线程池的核心行为。先看最完整的构造函数:
在这里插入图片描述

有好几个构造方法,一个比一个复杂,主要讲解第四个构造方法最多带有7个参数的,这个了解了其他的自然而然就知道了。

这 7 个参数决定了线程池的 “性格”,我们逐个拆解:

1. 核心线程数(corePoolSize)
  • 定义:线程池中长期存活的线程数量(除非设置了 allowCoreThreadTimeOut=true,核心线程才会超时销毁)。
  • 逻辑至少有多少个线程,线程池一创建,这些线程也要随之创建,直到整个线程池销毁,这些线程才会销毁。
  • 示例:核心线程数设为 2,意味着线程池会始终保持 2 个线程待命(即使空闲),优先处理新任务。
2. 最大线程数(maximumPoolSize)
  • 定义:线程池能创建的最大线程总数(核心线程数 + 非核心线程数)。
  • 逻辑:当核心线程满、任务队列也满时,会创建非核心线程处理任务;但总线程数不能超过这个值(不繁忙就销毁,繁忙就再创建)。
  • 注意:仅当任务队列满时,才会创建非核心线程(非核心线程是 “临时救兵”)。

就好比,线程池是公司,核心线程是正式员工,非核心线程是实习员工,需要的时候就招聘实习生,不需要的时候再裁掉。

3. 空闲线程存活时间(keepAliveTime)
  • 定义:非核心线程在空闲状态下的最大存活时间(核心线程默认不销毁,除非开启 allowCoreThreadTimeOut)。
  • 逻辑:非核心线程执行完任务后,若空闲时间超过这个值,就会被销毁,释放资源。
  • 示例:存活时间设为 30 秒,非核心线程空闲 30 秒后自动关闭。

就好比公司允许实习生摸鱼时间。

4. 时间单位(unit)
  • 定义:keepAliveTime 的时间单位,如 TimeUnit.SECONDS(秒)、TimeUnit.MILLISECONDS(毫秒)。
5. 工作队列(workQueue)
  • 定义:用于存放 “核心线程满后” 待执行的任务的阻塞队列(BlockingQueue 实现类)。

可选择使用数组(ArrayBlockingQueue)/链表(LinkedBlockingQueue),指定capacity,指定是否带有优先级/比较规则。线程池,本质上也是生产者消费者模型,调用submit就是在生产任务,线程池里的线程就是在消费任务。

6. 线程工厂(threadFactory)
  • 定义:执行程序创建新线程时使用的工厂 。(同一的构造并初始化线程)。
6.1 工厂模式

工厂模式也是一种设计模式,和单列模式是并列的关系,用来弥补构造方法的缺陷
在这里插入图片描述

假设我们求坐标类,已知道公式x=rcos(a),y=rsin(a),来求x y变量,但是构造方法的名字是固定的,想要提供不同的版本,就需要通过重载,但是有时候不一定能构成重载,就如下图。
在这里插入图片描述
而工厂方法的核心,通过静态方法,把构造对象new的过程歌词属性初始化的过程,封装起来了,提供多组静态方法,实现不同情况的构造。
在这里插入图片描述

在这里插入图片描述

提供工厂方法的类,就可以称为“工厂类”

7. 拒绝策略(RejectedExecutionHandler)
  • 定义:当 “任务队列满 + 最大线程数已满” 时,新提交的任务会被拒绝,此时执行的策略。

submit把任务添加到任务队列中,任务队列是堵塞队列,对于线程池来说,发现入队列操作时,队列满了,不会真的触发“人队列操纵”,不会真的阻塞,而是执行拒绝策略相关的代码。

在这里插入图片描述

  1. Java 默认提供 4 种策略:
    • AbortPolicy(默认):直接抛出 RejectedExecutionException,中断程序;
    • CallerRunsPolicy:由提交任务的主线程自己执行任务(减缓提交速度,避免任务丢失);
    • DiscardOldestPolicy:丢弃队列中最旧的任务(最早提交的任务),再尝试提交新任务。
    • DiscardPolicy:默默丢弃最新任务,不抛异常;

二、线程池的工作流程:从提交任务到执行的全链路

理解了核心参数后,线程池处理任务的流程就很清晰了。我们用一个例子(核心线程数 = 2,最大线程数 = 5,任务队列容量 = 3)来模拟:

  1. 提交任务 1、2:核心线程未满(2 个核心线程空闲),直接创建核心线程执行任务 1、2;
  2. 提交任务 3、4、5:核心线程已满(2 个都在忙),任务队列未满(容量 3),将任务 3、4、5 存入队列等待;
  3. 提交任务 6、7、8:核心线程满 + 任务队列满(已存 3 个),此时创建非核心线程(最多创建 3 个,因为最大线程数 = 5),用非核心线程执行任务 6、7、8;
  4. 提交任务 9:核心线程满 + 任务队列满 + 最大线程数满(5 个线程都在忙),触发拒绝策略(默认抛异常);
  5. 任务执行完成:若非核心线程空闲超过 keepAliveTime(如 30 秒),则销毁非核心线程,最终线程池回到 2 个核心线程待命的状态。
    用文字流程总结就是:
提交任务 → 核心线程是否空闲?→ 是:核心线程执行
                          → 否:任务队列是否未满?→ 是:任务入队
                                              → 否:总线程数是否未达最大?→ 是:创建非核心线程执行
                                                                      → 否:执行拒绝策略

三、用 Executors 创建常见线程池:快速上手实战

是不是觉得创建一个线程池真麻烦,还有简单办法:那就是Java标准库,也提供了另一组类,针对ThreadPoolExercutor进行了进一部封装,简化线程池的使用。
Executors 是 Java 提供的线程池 “工厂工具类”,它封装了 ThreadPoolExecutor 的复杂参数,通过静态方法快速创建常用线程池。但要注意:Executors 的默认配置有潜在风险,生产环境需谨慎使用(后文会说明)。

1. 固定大小线程池:newFixedThreadPool
  • **特点:**核心线程数和最大线程数是一样的
  • 代码演示
 public static void main(String[] args) {
       
        ExecutorService threadPool = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 10; i++) {
            int id = i;
            threadPool.submit(() -> {
                System.out.println("hello " + id + ", " + Thread.currentThread().getName());
            });
        }
    }
  • 输出结果
hello 2, pool-1-thread-3
hello 3, pool-1-thread-4
hello 0, pool-1-thread-1
hello 6, pool-1-thread-1
hello 1, pool-1-thread-2
hello 7, pool-1-thread-1
hello 5, pool-1-thread-4
hello 9, pool-1-thread-1
hello 4, pool-1-thread-3
hello 8, pool-1-thread-2
2. 可缓存线程池:newCachedThreadPool
  • **特点:**核心线程数 = 0,最大线程数 =Integer.MAX_VALUE(理论上无限)。
  • 代码示例
public static void main(String[] args) {

        ExecutorService threadPool = Executors.newCachedThreadPool();
        for (int i = 0; i < 20; i++) {
            int id = i;
            threadPool.submit(() -> {
                System.out.println("hello " + id + ", " + Thread.currentThread().getName());
            });
        }

    }
  1. 输出结果
hello 19, pool-1-thread-20
hello 3, pool-1-thread-4
hello 13, pool-1-thread-14
hello 10, pool-1-thread-11
hello 14, pool-1-thread-15
hello 18, pool-1-thread-19
hello 16, pool-1-thread-17
hello 12, pool-1-thread-13
hello 11, pool-1-thread-12
hello 15, pool-1-thread-16
hello 5, pool-1-thread-6
hello 17, pool-1-thread-18
hello 4, pool-1-thread-5
hello 9, pool-1-thread-10
hello 1, pool-1-thread-2
hello 8, pool-1-thread-9
hello 6, pool-1-thread-7
hello 7, pool-1-thread-8
hello 0, pool-1-thread-1
hello 2, pool-1-thread-3
  1. 风险 :最大线程数无限,任务密集时可能创建过多线程,导致 CPU / 内存耗尽。

四、总结

  1. 核心认知:线程池的本质是 “线程复用 + 任务管理”,通过参数配置平衡性能与资源占用。
  2. Executors 的坑:newFixedThreadPool的无界队列可能导致 OOM;newCachedThreadPool 的无限线程可能耗尽资源。
    线程池是 Java 并发的 “基石”,掌握它的参数、流程和实战用法,能让你的并发程序更高效、更稳定。
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐