线程池的由来

最开始大家使用线程都是

new Thread(r).start();复制代码

如果程序中只有很少的地方需要使用多线程这种方式没有问题,但如果很多地方都需要使用到多线程,你可能也会想能不能写个工具类,让工具类来管理线程的创建、销毁,而我们只需要使用即可呢?

这个工具类,就被称为线程池!

第一版 最简单的线程池

让我们来设计这个工具类,并一步一步慢慢将它完善成线程池。

从最简单的开始,跟 ThreadPoolExecutor 线程池一样,我们的工具类也实现 Executor 接口

public interface Executor {public void execute(Runnable r);
}复制代码

最简单的版本

// 新线程:直接创建一个新线程运行class FlashExecutor implements Executor {public void execute(Runnable r) {new Thread(r).start();
    }
}复制代码

虽然简陋,但已经有了核心的功能,帮我们创建线程并执行任务

第二版 复用线程

上面的版本最大的问题在于,每来一个 Runnable 就会创建一个新的线程,这显然是不合适的最好是能复用现有的线程。

怎么复用呢?我们可以只创建一个线程(Worker),让它处理所有任务,最大限度复用已有线程。

别小看这一点小小的改动,它解决两大问题

  1. 限制了线程数量
  2. 解决了每次重复创建和销毁线程带来的系统开销

但是这样做又会引出新的问题,所有任务都让一个线程来处理,处理不过来怎么办?第三版就是要解决这个问题

第三版 添加缓冲队列

如果任务太多,一个线程处理不过来,我们可以加上一个任务(tasks)队列做缓冲,新来的任务,丢进队里了,等待处理。

这样线程就可以慢慢处理,处理完一个,再从队列里取出下一个处理。

ThreadPoolExecutor 线程池核心思想_线程池

任务队列不但起到了缓冲的作用,最重要的是,它将任务的提交与执行解耦了。

有了任务队列,就可以放心了吗,如果任务还是太多,加上任务队列,一个线程还是出来不过来该怎么办?

第四版 自定义核心线程数

任务队列确实能解耦任务的提交与执行,并起到缓冲作用,但是无法解决单线程处理能力不足的问题。解决这个问题的唯一版本就是增加线程数量。

但具体要设置多少个线程呢,线程太少,任务处理速度太慢,任务队列里的任务堆积,线程太多空闲线程占用系统资源。这个线程数量应该要跟任务量相匹配,不宜过多或过少,具体数量应该交由用户自己去评估。

因此我们应该把线程数量做为该工具类构造方法中的一个参数提取出来,交给用户去设置,我们就叫它 corePoolSize(核心线程数) 好了

经过这般设计之后,整个流程如下

  1. 初始化线程池时,直接启动 corePoolSize 个工作线程(Worker) 先跑着(待优化)
  2. 这些 Worker 死循环的从队列里取任务然后执行
  3. execute 方法仍然是直接把任务放到队列,但队列满了之后直接抛弃(第五版来解决这个问题)

ThreadPoolExecutor 线程池核心思想_ThreadPoolExecutor_02

第五版 增加拒绝策略、按需创建线程

虽然我们已经将 corePoolSize(核心线程数) 交给用户去根据任务量来评估、设置。但是毕竟任务量这个东西想精确评估本身就比较困难,并且任务量是波动的,因此用户设置的 corePoolSize(核心线程数) 很可能并不匹配,或者不能一直匹配任务速率。

这个问题,不管使用什么办法都无法彻底消灭,我们只能通过合理的设置,减小它出现的概率,已及通过一些方法,解决它出现后带来的影响

在第四版,任务处理不过来 任务(tasks)队列 满后,是直接丢弃任务,这肯定是不行的。我们需要对这些无法入队的任务进行处理,但具体应该怎么处理,显然不应该由我们决定,而是应该交给用户去处理。

我们可以设计一个接口, 比如就叫 RejectedExecutionHandler ,它有一个 rejectedExecution 方法,这里面无法入队任务的具体处理逻辑。

然后我们可以在构造方法中增加一个入参,类型是接口 RejectedExecutionHandler,由调用者决定实现类,以便在任务提交失败后执行 rejectedExecution 方法。

为了方便,我们称这套方案为拒绝策略

除了增加 拒绝策略 策略之外,第四版中,初始化时,直接启动 corePoolSize 个工作线程(Worker) 也不太合适,如果刚开始没有多少任务需要执行,那么就会有很多空闲的线程占用系统资源。

我们可以在初始化线程池时,不立刻创建 corePoolSize 个工作线程,而是等待调用者不断提交任务的过程中,逐渐把工作线程 Worker 创建出来,等数量达到 corePoolSize 时就停止。

我们用一个属性来记录已经创建出来的工作线程数量,就叫 workCount

另外,创建线程,也不在通过 new 的方式,我们在构造方法中增加一个入参,类型是一个接口 ThreadFactory,增加工作线程时调用这个由调用者传入的 ThreadFactory 实现类的 newThread 方法。这样一来想用什么样的线程,也可以由调用者自己决定。

如上般改动后,线程池整个初始化及工作流程如下

ThreadPoolExecutor 线程池核心思想_ThreadPoolExecutor_03

这版我们有三大改动

  1. 加拒绝策略:任务队列满后,无法入队的任务可以由调用者自行处理
  2. 按需创建线程:初始化不在启动全部线程,而是按需加载
  3. 增加线程工厂:想要什么样的线程可以有用户自己决定

第六版 弹性扩容线程池

第五版中提到,"任务量是有波动的",每当高峰期来临时,线程池执行任务的速率往往是跟不上任务产生的速率,任务队列被塞满。大量任务触发拒绝策略,这不是我们想看到的,拒绝策略应该当做最终手段,不到万不得已不应该让它触发。ThreadPoolExecutor 线程池核心思想_线程池_04

那有什么办法解决这个问题呢?接这类问题无非就是两种思路

  1. 降低产生任务的速率
  2. 增加任务处理的速率

不过既然是在高峰期,降低产生任务的速率这条路肯定是走不通了,那就只能增加任务处理的速率!

怎么增加任务处理的速率呢?  增加线程数呗!

我们可以预估任务的锋值,然后设置对应的核心线程数,由于第五版添加了按需加载线程功能,峰值来临之前,并不会有多少空闲线程出现,但是峰值过后会有大量空闲线程,直到下次峰值来临之前,他们都会一直占有系统资源。ThreadPoolExecutor 线程池核心思想_ThreadPoolExecutor_05

既然如此,我们都可以想到一个优化点,在峰值过后把这些空闲线程清理掉,等下次峰值来临时,在把他们创建出来!但这种线程我们就不能称它为核心线程了,我们称它为非核心线程

我们在构造方法中增加一个新参数,叫 maximumPoolSize (最大线程数) ,它是由调用方预估任务峰值并结合 cpu 情况得来的,它和 corePoolSize(核心线程数) 的差值,就是 非核心线程数。

线程池运行时,当核心线程数和队列都满了,新提交的任务仍然可以通过创建新的工作线程(非核心线程),直到工作线程数达到 maximumPoolSize 为止,这样就可以缓解一时的高峰期了,而用户也不用设置过大的核心线程数,造成非高峰期的线程空闲ThreadPoolExecutor 线程池核心思想_线程池_06

整个流程如下

  1. 开始的时候和上一版一样,当 workCount < corePoolSize 时,通过创建新的 Worker 来执行任务。
  2. 当 workCount >= corePoolSize 就停止创建新线程,把任务直接丢到队列里。
  3. 但当队列已满并且 workCount < maximumPoolSize 时,不再直接走拒绝策略,而是创建非核心线程,直到 workCount = maximumPoolSize,再走拒绝策略。

ThreadPoolExecutor 线程池核心思想_ThreadPoolExecutor_07

高峰期过后,大量空闲的线程占用系统资源,我们需要对其销毁以节约系统资源。那些线程可以被销毁呢?核心线程肯定不行,我们只能销毁非核心线程

知道了要销毁的对象后,下一个问题是什么时候销毁呢?

我们可以给非核心线程设定一个 keepAliveTime 超时时间 ,当这么长时间都没能从队列里获取任务时,就不再等了,销毁线程。ThreadPoolExecutor 线程池核心思想_ThreadPoolExecutor_08

总结

最后我们来看下这个经过多轮设计之后的线程工具类

它的构造方法如下

public FlashExecutor(
        // 核心线程数int corePoolSize,
        // 最大线程数int maximumPoolSize,
        // 超时时间long keepAliveTime,
        // 超时时间的单位
        TimeUnit unit,
        // 任务队列
        BlockingQueue<Runnable> workQueue,
        // 线程工厂
        ThreadFactory threadFactory,
        // 拒绝策略
        RejectedExecutionHandler handler
) {this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;
}复制代码

整个任务提交流程如下

ThreadPoolExecutor 线程池核心思想_ThreadPoolExecutor_09

这个工具类,其实就是 java 中的 ThreadPoolExecutor,上面的那个构造方法,其实就是 ThreadPoolExecutor 中最长的那个构造方法,参数名都没变。