2024-03-23
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/mianshi/baodian/detail/9653005356

线程池是一种池化的技术,其核心思想是为了减少每次获取和结束资源的消耗,提高对资源的利用率。

线程池的基本概念

  • 线程重用:线程池中的线程在执行完任务后不会被销毁,而是可以被重新利用来执行新的任务。
  • 任务队列:当所有线程都在忙碌时,新的任务会被放入一个队列中等待执行。
  • 线程管理:包括线程的创建、销毁、任务分配等。

线程池的好处

  • 降低资源消耗:通过重复利用现有的线程来执行任务,减少了线程创建和销毁的开销。
  • 提高响应速度:线程的创建是需要时间和资源的,利用现有线程执行任务,省去了创建线程的时间,拿到任务后可以立刻执行。
  • 提高线程的可管理性:线程是一种稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。

线程池的使用

public class ThreadPoolTest {
    public static void main(String[] args) throws Exception {
        //1. 创建线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                5,
                5,
                0L,
                TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

        // 2. 往线程池中提交任务
        for (int i = 0; i < 10; i++) {
            threadPoolExecutor.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " - Java 面试 600 讲");
            });
        }

        // 3. 关闭线程池
        threadPoolExecutor.shutdown();
    }
}

执行结果:

从上面可以看出,线程池的使用非常简单:

  • 调用new ThreadPoolExecutor() 构造方法,指定核心参数,创建线程池。
  • 调用 execute() 方法提交 Runnable 任务。
  • 使用结束后,调用 shutdown() 方法,关闭线程池。

对于线程池而言,其核心参数非常重要,这也是我们面试线程池必问的一个问题。

线程池核心参数

线程池共有七大核心参数:

参数名称 参数含义
int corePoolSize 核心线程数
int maximumPoolSize 最大线程数
long keepAliveTime 线程存活时间
TimeUnit unit 时间单位
BlockingQueue workQueue 阻塞队列
ThreadFactory threadFactory 线程创建工厂
RejectedExecutionHandler handler 拒绝策略

1、 corePoolSize:核心线程数

当线程池中的线程数小于 corePoolSize 时,即使有空闲线程,线程池也会创建新线程来处理新的任务。默认情况下,空闲状态的线程是不会被回收的,所以 corePoolSize 是线程池中始终保持活跃的线程数量。但是我们可以通过一个参数 allowCoreThreadTimeOut 来控制核心线程的行为。当该参数被设置为 true 时,在空闲时间超过 keepAliveTime 时,核心线程也会被终止并回收。

举个例子:如果你有一个核心线程数为 5,最大线程数为 10 的线程池,且 allowCoreThreadTimeOut 设置为 false(默认情况),那么即使没有任务执行,线程池中也始终会有 5 个线程活跃。但如果将 allowCoreThreadTimeOut 设置为 true,那么这些核心线程在空闲超过 keepAliveTime 后会被终止回收,可能导致线程池中暂时没有任何线程。

这个参数比较适用于哪些“间歇性”的场景,即有时非常忙有时又完全空闲,在这种情况下,设置 allowCoreThreadTimeOut = true,可以在系统空闲时节约资源。

2、maximumPoolSize:最大线程数

这是线程池中允许的最大线程数量。

当线程池中的线程数达到 corePoolSize ,阻塞队列又满了之后,才会继续创建线程,直到达到 maximumPoolSize。另外空闲的非核心线程会被回收。

3、keepAliveTime:线程存活时间

当线程池中线程数量超过 corePoolSize 时,这是超出部分线程在空闲时的存活时间。

如果线程空闲时间超过 keepAliveTime,线程将被销毁,直到线程池中的线程数降至 corePoolSize。注意,这是默认情况下。

4、unit:时间单位

keepAliveTime 参数的时间单位,默认是TimeUnit.MILLISECONDS(毫秒),可选择的有:

  • TimeUnit.NANOSECONDS(纳秒)
  • TimeUnit.MICROSECONDS(微秒)
  • TimeUnit.MILLISECONDS(毫秒)
  • TimeUnit.SECONDS(秒)
  • TimeUnit.MINUTES(分钟)
  • TimeUnit.HOURS(小时)
  • TimeUnit.DAYS(天)

5、workQueue:阻塞队列

存放待处理任务的阻塞队列。当线程池中的线程数达到corePoolSize,再提交的任务就会放到阻塞队列的等待,默认使用的是LinkedBlockingQueue,可选择的有:

  • LinkedBlockingQueue(基于链表实现的阻塞队列)
  • ArrayBlockingQueue(基于数组实现的阻塞队列)
  • SynchronousQueue(只有一个元素的阻塞队列)
  • PriorityBlockingQueue(实现了优先级的阻塞队列)
  • DelayQueue(实现了延迟功能的阻塞队列)

6、threadFactory:线程创建工厂

用于创建新线程的工厂,默认的是Executors.defaultThreadFactory()。利用它,我们可以自定义线程创建的过程,例如设置线程名、优先级等。

创建线程的时候最好指定线程名称,便于排查问题。

7、handler:拒绝策略

当线程池中的线程数达到maximumPoolSize,阻塞队列也满了之后,再往线程池中提交任务,就会触发执行拒绝策略。常见的拒绝策略有:

  • AbortPolicy(直接终止,抛出异常,默认)
  • CallerRunsPolicy(返回给调用者执行)
  • DiscardPolicy(默默丢弃,不抛出异常)
  • DiscardOldestPolicy(丢弃队列中最旧的任务,执行当前任务)

线程池执行过程

  1. 首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。
  2. 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务,即便有空闲线程。
  3. 如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。
  4. 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
  5. 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。
阅读全文