🌄写在前面

什么是线程池?

线程池是一种基于池化思想的线程管理工具,常常用在多线程服务器上。如果不使用线程管理工具,可能会产生下面的问题:

  • 线程过多会带来额外的开销,其中包括创建销毁线程的开销切换线程上下文的开销等等,降低了计算机的整体性能

而使用线程池维护多个线程,等待监督管理者分配可并发执行的任务。一方面避免了处理任务时创建销毁线程开销的代价,另一方面避免了线程数量膨胀导致的过分调度问题,保证了对内核的充分利用。总的来说使用线程池的好处:

  • 降低资源开销:每次任务到达时不需要重新创建和销毁
  • 提高可管理性:统一对线程进行调优、监控
  • 提高任务响应速度:不需要等待线程创建

线程池解决了什么问题?

线程池解决的核心问题就是资源管理问题。在并发环境下,系统不能够确定在任意时刻中,有多少任务需要执行,有多少资源需要投入。这种不确定性将带来以下若干问题:

  1. 频繁申请/销毁资源和调度资源,将带来额外的消耗,可能会非常巨大。
  2. 对资源无限申请缺少抑制手段,易引发系统资源耗尽的风险。
  3. 系统无法合理管理内部的资源分布,会降低系统的稳定性。

    线程池设计思路

    总体设计

    | 此次分析是以 JDK1.8ThreadPoolExecutor 为准。该类是整个线程池实现的核心,我们先根据右边的 ThreadPoolExecutor 的UML类图,来了解一下它的继承关系。
    - Executor 提供任务执行的功能,这部分专门负责任务的执行,让后续开发者只需关注任务的编写和提交即可
    - ExecutorService 提供拓展了两项功能,一是批量执行任务功能;二是提供了结束线程的能力
    - AbstractExecutorService 是一个抽象类,将上述两个接口的能力进行了整合,保证下层只需要关注一个任务执行方法
    - ThreadPoolExecutor 是最终的实现类,它维护着整个池子的状态,同时管理线程和任务,使两者相互配合共同完成任务的执行
    | image.png | | —- | —- | | |

运行机制

线程池运行机制少不了两个部分:

  1. 线程的管理
  2. 任务的处理

在总体设计里,我们可以看到 ExecutorExecutorServiceAbstractExecutorService 一直在强化任务的执行;那么线程管理的重任自然就落在了 ThreadPoolExecutor ,所以这部分我将分为两个部分讲:

  1. ThreadPoolExecutor 的管理机制
  2. AbstractExecutorService 的执行原理

ThreadPoolExecutor 机制概览

🧺J.U.C - 线程池实现与设计之设计篇 - 图2
FROM 《Java线程池实现原理及其在美团业务中的实践》
ThreadPoolExecutor 的整个机制有点类似生产消费模型,外部充当生产者,内部的线程池充当消费者,每当外部有任务提交进来后,ThreadPoolExecutor 会对其进行任务分配:从池子里取一个线程,让该线程执行任务后,执行完毕后将线程归还给池子。在 ThreadPoolExecutor 运行中,任务的分配方式可以分为三种情况:

  1. 如果线程足够,可以直接执行
  2. 如果线程不足,则将消息放入任务队列,线程池从队列中获取任务进行执行
  3. 如果任务数量超出任务队列,则任务拒绝

线程池运行状态

ThreadPoolExecutor 在内部使用一个32位整型变量来维护 运行状态(RunState)线程数量(ThreadCount) 两个值。具体是保存在下面这个变量上:

  1. private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

通过这个变量初始值可以看出,线程池新建时处于 **RUNNING** 状态线程数量为 0。这两个状态通过 ctlOf() 静态方法进行 或运算 得出:

private static int ctlOf(int rs, int wc) { return rs | wc; }

它总共有五种状态:

运行状态 描述
RUNNING 能接受新的任务,也能处理已经入队了的任务
SHUTDOWN 不接受新的任务,但是还能处理已经入队了的任务
STOP 不接受新的任务,也不处理已经入队列的任务,会中断所有正在处理中的任务
TIDYING 所有的任务都已中止, workCount (工作线程数量)为0
TERMINATED terminated() 方法执行完成后进入该状态

其状态转换图如下所示:
🧺J.U.C - 线程池实现与设计之设计篇 - 图3
FROM 《Java线程池实现原理及其在美团业务中的实践》
关于线程池状态的具体实现可以看《🧺J.U.C - 线程池实现与设计之实现篇》

任务调度

任务调度是所有任务提交的入口。当用户提交了一个任务后,线程池会根据当前线程池的状态来决定如何执行这个任务,检查状态的依据主要由以下三个方面决定:

  • 运行状态
  • 运行线程数量
  • 运行策略

状态检查流程如下所示:
点击查看【processon】

  • 任务提交进来时,首先检查线程池的状态,仅为 RUNNING 才继续向后流转;否则,直接拒绝任务提交
  • 当前运行线程数 小于 核心数 **CoreSize** 时,会新建一个工作线程用于执行新任务;反之继续向下流转
  • 阻塞队列未满 时,则将任务提交到阻塞队列中等待;反之继续向下流转
  • 当 线程数小于最大线程数 时,则继续新建工作线程用于执行新任务;反之拒绝任务的提交

参考资料