线程池ThreadPoolExecutor详解
Published in:2025-04-16 |

线程池ThreadPoolExecutor详解

一、什么是线程

先来看枯燥的定义:线程是操作系统能够进行运算调度的最小单位,是进程中的实际运作单元,共享进程资源但拥有独立的执行上下文。

通俗来说,线程必须依赖于进程而存在,多个线程可以并发执行,共享进程资源,具体区别可以看下表所示:

对比维度 程序(Program) 进程(Process) 线程(Thread)
定义 静态的代码和数据集合(如 .exe.class 文件),未运行时存于磁盘。 程序的一次动态执行实例,是操作系统分配资源的基本单位。 进程内的一条执行路径,是程序执行的最小调度单位。
存在形式 静态文件,不占用系统资源(CPU、内存等)。 动态实体,占用独立的内存空间、系统资源(如文件句柄、CPU 时间片)。 动态实体,依附于进程存在,共享进程的资源(代码段、数据段等),仅拥有独立的栈和寄存器状态。
资源分配 无,仅存储代码和数据,未激活时不涉及资源。 拥有独立的地址空间、内存、文件句柄、环境变量等资源,与其他进程隔离。 不独立分配资源,共享所属进程的资源(如打开的文件、全局变量),仅独占栈空间和线程本地存储(TLS)。
调度单位 不参与调度(非运行态)。 操作系统早期的调度单位(现在多为线程),调度开销较大。 现代操作系统的基本调度单位,调度开销小(上下文切换更快)。
独立性 无运行概念,不依赖进程 / 线程(仅作为文件存在)。 独立运行,不同进程间通过 IPC(进程间通信)协作,资源互不干扰。 不能独立存在,必须依附于进程,依赖进程提供运行环境(如内存空间、资源句柄)。
并发性 无并发性(静态文件)。 可包含多个线程,通过多线程实现内部并发;也可通过多进程实现并发(需 IPC)。 作为进程内的执行单元,多个线程可并发执行(CPU 分时或多核并行)。
生命周期 持久存在(除非被删除)。 动态创建(如 fork()、双击程序启动),动态销毁(如进程结束或崩溃)。 动态创建(如 pthread_create()、Java new Thread()),动态销毁(如线程结束或进程终止)。
举例 磁盘上的 微信.exeAndroidManifest.xml(未运行)。 运行中的微信程序(一个进程)、Android 应用进程(如 com.tencent.wechat)。 微信进程中的网络请求线程、UI 渲染线程、消息处理线程等。
本质 “静态的指令集合” “运行中的程序实例”(资源容器) “进程内的执行路径”(任务单元)

二、为什么需要线程池

今天需要讨论的是线程池,那么我们为什么需要用到线程池呢?为什么不直接通过Thread.start来创建和启动线程来执行任务呢?总结起来大概有以下3个原因

  • 线程创建销毁的成本高:如果频繁处理短耗时任务(如每次请求新建一个线程),创建 / 销毁的开销可能远大于任务执行本身,导致性能瓶颈。
  • 减少上下文切换开销:操作系统在调度线程时,需要保存 / 恢复线程的执行上下文(寄存器、栈指针等),大量线程频繁切换会占用大量 CPU 时间。
  • 防止无限创建线程导致系统崩溃:若直接为每个任务创建新线程,当任务量突然激增(如高并发请求),可能瞬间创建成百上千个线程,耗尽内存、CPU 资源或达到系统线程数限制(如 Linux 默认线程数限制约 1 万)。

三、ThreadPoolExecutor

3.1 线程池的状态

线程池的状态 说明
RUNNING 允许提交并处理任务
SHUTDOWN 不允许提交新的任务,但是会处理完已提交的任务
STOP 不允许提交新的任务,也不会处理阻塞队列中未执行的任务,并设置正在执行的线程的中断标志位
TIDYING 所有任务执行完毕,池中工作的线程数为0,等待执行terminated()的方法
TERMINATED terminated()方法执行完毕

改变线程池状态的方法:

1
2
exexutor.shutdown() // 将线程池由 RUNNING(运行状态)转换为 SHUTDOWN状态
exexutor.shutdownNow() // 将线程池由RUNNING 或 SHUTDOWN 状态转换为 STOP 状态。

注:SHUTDOWN 状态 和 STOP 状态 先会转变为 TIDYING 状态,最终都会变为 TERMINATED

获取线程池状态的方法:

1
2
fixedExecutor.isShutdown // 检查执行器服务是否已经调用了 shutdown() 或者 shutdownNow() 方法。调用这两个方法之后,执行器服务会停止接受新任务,但已经提交的任务会继续执行。
executor.isTerminated // 检查执行器服务是否已经完全终止。只有在调用 shutdown() 或者 shutdownNow() 方法,并且所有已提交的任务都执行完毕之后,该方法才会返回 true。

3.2 线程池各参数的含义

定义线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
val executor = ThreadPoolExecutor(
/**
* corePoolSize: 核心线程数
* 空闲不会被回收,除非设置了:executor.allowCoreThreadTimeOut(true)
*/
2,
/**
* maximumPoolSize: 最大线程数
* 创建的线程总数不允许超过maximumPoolSize
*/
3,
/**
* keepAliveTime: 非核心线程允许的空闲时间,超过就会被线程池回收
* 但如果设置了allowCoreThreadTimeOut(true),核心线程也会被回收
*/
60,
/**
* unit: 时间计数单位
*/
TimeUnit.SECONDS,

* workQueue: 任务队列
* 当创建的线程超过核心线程数且没有线程空闲时,则会将任务放在队列中
*/
ArrayBlockingQueue(1),
/**
* handler: 任务超过线程池负荷的处理手段
* 当池子中创建的线程数超过maximumPoolSize & workQueue已满的情况下,就会启用handler机制
*/
DiscardOldestPolicy()
)

线程池的主要处理流程

线程池的主要处理流程

当调用线程池execute() 方法添加一个任务时,线程池会做如下判断:

  • 如果有空闲线程,则直接执行该任务;
  • 如果没有空闲线程,且当前运行的线程数少于corePoolSize,则创建新的线程执行该任务;
  • 如果没有空闲线程,且当前的线程数等于corePoolSize,同时阻塞队列未满,则将任务入队列,而不添加新的线程;
  • 如果没有空闲线程,且阻塞队列已满,同时池中的线程数小于maximumPoolSize ,则创建新的线程执行任务;
  • 如果没有空闲线程,且阻塞队列已满,同时池中的线程数等于maximumPoolSize ,则根据构造函数中的 handler 指定的策略来拒绝新的任务。

3.3 重要参数解析

KeepAliveTime
  • keepAliveTime :表示空闲线程的存活时间
  • TimeUnit unit :表示keepAliveTime的单位

当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

注:如果线程池设置了allowCoreThreadTimeout参数为true(默认false),那么当空闲线程超过keepaliveTime后直接停掉。(不会判断线程数是否大于corePoolSize)即:最终线程数会变为0。

workQueue 任务队列

决定了缓存任务的排队策略

ThreadPoolExecutor线程池推荐了三种等待队列,它们是:SynchronousQueue 、LinkedBlockingQueue和 ArrayBlockingQueue

1)有界队列:

  • SynchronousQueue :一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于 阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool 使用了这个队列。
  • ArrayBlockingQueue:一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。

2)无界队列:

  • LinkedBlockingQueue:基于链表结构的无界阻塞队列,它可以指定容量也可以不指定容量(实际上任何无限容量的队列/栈都是有容量的,这个容量就是Integer.MAX_VALUE)
  • PriorityBlockingQueue:是一个按照优先级进行内部元素排序的无界阻塞队列。队列中的元素必须实现 Comparable 接口,这样才能通过实现compareTo()方法进行排序。优先级最高的元素将始终排在队列的头部;PriorityBlockingQueue 不会保证优先级一样的元素的排序。

注意:keepAliveTime和maximumPoolSize及BlockingQueue的类型均有关系。如果BlockingQueue是无界的,那么永远不会触发maximumPoolSize,自然keepAliveTime也就没有了意义。

threadFactory

指定创建线程的工厂。(可以不指定)

如果不指定线程工厂时,ThreadPoolExecutor 会使用ThreadPoolExecutor.defaultThreadFactory 创建线程。默认工厂创建的线程:同属于相同的线程组,具有同为 Thread.NORM_PRIORITY 的优先级,以及名为 “pool-XXX-thread-” 的线程名(XXX为创建线程时顺序序号),且创建的线程都是非守护进程。

handler 拒绝策略:

handler :表示当 workQueue 已满,且池中的线程数达到 maximumPoolSize 时,线程池拒绝添加新任务时采取的策略。(可以不指定)

策略 解析
ThreadPoolExecutor.AbortPolicy() 抛出 RejectedExecutionException 异常。默认策略
ThreadPoolExecutor.CallerRunsPolicy() 由向线程池提交任务的线程来执行该任务
ThreadPoolExecutor.DiscardPolicy() 抛弃当前的任务
ThreadPoolExecutor.DiscardOldestPolicy() 抛弃最旧的任务(最先提交而没有得到执行的任务)

最科学的的还是 AbortPolicy 提供的处理方式:抛出异常,由开发人员进行处理。

参考来源:博客_ThreadPoolExecutor详解

Next:
hexo框架+github如何创建属于自己的博客