一、线程池定义

1.1 线程池是什么

线程池是一种基于池化思想管理线程的工具。

线程池的好处:

  • 降低资源消耗: 重复利用已创建的线程,降低线程创建和销毁的损耗;
  • 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
  • 提高线程的可管理性:线程是稀缺资源,如果随意创建,不仅消耗系统资源,还会由于线程的分布不合理导致资源调度的失衡,降低系统稳定性。使用线程池可以进行统一的分配、调优、监控。
  • 提供其他功能:线程池具有可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor就允许任务延迟或定时执行。

1.2 线程池解决什么问题

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

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

为了解决资源分配这个问题,线程池采用“池化”思想。池化,顾名思义是为了最大化收益最小化风险而将资源统一管理的一种思想。

二、线程池的核心设计与实现

线程池在java中体现是ThreadPoolExecutor类

2.1 总体设计

Java中线程池核心实现类是ThreadPoolExecutor类,我们针对JDK1.8进行分析,UML类图如下
image.png
图1 ThreadPoolExecutor UML类图

Executor:顶层接口Executor中提供了一个execute()提交任务,它将任务的提交和执行进行解耦,用户无需关心如何创建线程,以及如何调度线程来执行任务。用户只需要提供Runnable对象,将任务的运行逻辑提交到执行器(Executor)中,由Executor框架完成线程的调配和任务的执行部分。
ExecutorService: ExecutorService接口增加了一些能力

  • 扩充执行任务的能力,补充可以为一个或者一批异步任务生成Future的方法
  • 提供管控线程池的方法,比如停止线程池的运行。

AbstractExecutorService:上层抽象类,将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可。
ThreadPoolExecutor:最下层的实现类,实现最复杂的运行部分,ThreadPoolExecutor一方面维护自身的生命周期,同时管理线程和任务,使两者良好的结合从而执行并行任务。

ThreadPoolExecutor运行机制如下图
image.png
线程池内部在内部实际构造了一个生产者消费者模型,将线程和任务两者解耦。从而良好的缓冲任务复用线程。
线程池的运行主要分成两部分:任务管理、线程管理。
任务管理:充当生产者的角色,当任务提交后,线程池会判断该任务的后续流转

  1. 直接申请线程执行该任务
  2. 缓冲到阻塞队列中等待线程执行

    2.2 生命周期管理

    线程池的状态并不是用户显式设置的,而是伴随着线程池的运行,由内部来维护。线程池内部使用一个变量维护两个值:运行状态(runState)和线程数量(workerCount)。具体代码如下
    1. private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    ctl这个是AtomicInteger类型,是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段,同时包含两部分信息:runState workerCount,高三位保存runState,低29位保存workerCount,两个变量之间互不干扰。用一个变量去存储两个值,可以避免在做相关决策时,出现不一致的情况,而占用锁资源。
    ThreadPoolExecutor的运行状态有5种
运行状态 状态描述
RUNNING 能够接受新提交的任务,并且也能处理阻塞队列种的任务。
SHUTDOWN 关闭状态,不再接受新提交的任务,可继续处理阻塞队列中的任务。
STOP 不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。
TIDYING 所有任务都已终止,workerCount(有效线程数)为0。
TERMINATED terminated()方法执行完后进入该状态。

生命周期转换如下图所示

2.3任务执行机制

2.3.1 任务调度

任务调度是线程池的入口,当用户提交一个任务,接下来任务如何执行由任务调度决定。任务调度是线程池的核心运行机制。
任务调度由execute方法完成,主要工作是:检查线程池状态、运行线程数、运行策略决定接下来的流程。

2.3.2 任务缓冲

任务缓冲使用BlockingQueue阻塞队列实现,使用不同的队列可以实现不一样的任务存取策略。、
这块之后对于常见的阻塞队列详细学习整理

2.3.3 任务申请

任务的执行有两种可能

  1. 任务直接由新创建的线程执行。(新建线程的情况)
  2. 线程从队列中获取任务然后执行,执行完任务再冲队列中获取任务执行,循环往复。 (大多数情况下)

线程需要从任务缓存模块中不断的读取任务执行,帮助线程从阻塞队列中获取任务,实现线程模块与任务管理模块之间的通信。这部分策略由getTask()方法实现,执行流程如下