Java-Concurrent-ThreadPoolExecutor
在日常的开发调试中,我们经常会直接new一个Thread对象来执行某个任务。这种方式在任务数较少的情况下比较简单实用,但是在并发量较大的场景中却有着致命的缺陷。例如在访问量巨大的网站中,如果每个请求都开启一个线程来处理的话,即使是再强大的服务器也支撑不住。一台电脑的CPU资源是有限的,在CPU较为空闲的情况下,新增线程可以提高CPU的利用率,达到提升性能的效果。但是在CPU满载运行的情况下,再继续增加线程不仅不能提升性能,反而因为线程的竞争加大而导致性能下降,甚至导致服务器宕机。因此,在这种情况下我们可以利用线程池来使线程数保持在合理的范围内,使得CPU资源被充分的利用,且避免因过载而导致宕机的危险。在Executors中为我们提供了多种静态工厂方法来创建各种特性的线程池,其中大多数是返回ThreadPoolExecutor对象。因此本篇我们从ThreadPoolExecutor类着手,深入探究线程池的实现机制。
1. 线程池状态和线程数的表示
//高3位表示线程池状态, 后29位表示线程个数
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//运行状态 例:11100000000000000000000000000000
private static final int RUNNING = -1 << COUNT_BITS;
//关闭状态 例:00000000000000000000000000000000
private static final int SHUTDOWN = 0 << COUNT_BITS;
//停止状态 例:00100000000000000000000000000000
private static final int STOP = 1 << COUNT_BITS;
//整理状态 例:01000000000000000000000000000000
private static final int TIDYING = 2 << COUNT_BITS;
//终止状态 例:01100000000000000000000000000000
private static final int TERMINATED = 3 << COUNT_BITS;
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
在继续接下来的探究之前,我们先来搞清楚ThreadPoolExecutor是怎样存放状态信息和线程数信息的。ThreadPoolExecutor利用原子变量ctl来同时存储运行状态和线程数的信息,其中高3位表示线程池的运行状态(runState),后面的29位表示线程池中的线程数(workerCount)。上面代码中,runStateOf方法是从ctl取出状态信息,workerCountOf方法是从ctl取出线程数信息,ctlOf方法是将状态信息和线程数信息糅合进ctl中。具体的计算过程如下图所示。

2. 线程池各个状态的具体含义
就像人的生老病死一样,线程池也有自己的生命周期,从创建到终止,线程池在每个阶段所做的事情是不一样的。新建一个线程池时它的状态为Running,这时它不断的从外部接收并处理任务,当处理不过来时它会把任务放到任务队列中;之后我们可能会调用shutdown()来终止线程池,这时线程池的状态从Running转为Shutdown,它开始拒绝接收从外部传过来的任务,但是会继续处理完任务队列中的任务;我们也可能调用shutdownNow()来立刻停止线程池,这时线程池的状态从Running转为Stop,然后它会快速排空任务队列中的任务并转到Tidying状态,处于该状态的线程池需要执行terminated()来做相关的扫尾工作,执行完terminated()之后线程池就转为Terminated状态,表示线程池已终止。这些状态的转换图如下所示。

3. 关键成员变量的介绍
//任务队列
private final BlockingQueue<Runnable> workQueue;
//工作者集合
private final HashSet<Worker> workers = new HashSet<Worker>();
//线程达到的最大值
private int largestPoolSize;
//已完成任务总数
private long completedTaskCount;
//线程工厂
private volatile ThreadFactory threadFactory;
//拒绝策略
private volatile RejectedExecutionHandler handler;
//闲置线程存活时间
private volatile long keepAliveTime;
//是否允许核心线程超时
private volatile boolean allowCoreThreadTimeOut;
//核心线程数量
private volatile int corePoolSize;
//最大线程数量
private volatile int maximumPoolSize;
//默认拒绝策略
private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
在深入探究线程池的实现机制之前,我们有必要了解一下各个成员变量的作用。上面列出了主要的成员变量,除了一些用于统计的变量,例如largestPoolSize和completedTaskCount,其中大部分变量的值都是可以在构造时进行设置的。下面我们就看一下它的核心构造器。
//核心构造器
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) {
throw new IllegalArgumentException();
}
if (workQueue == null || threadFactory == null || handler == null) {
throw new NullPointerException();
}
this.corePoolSize = corePoolSize; //设置核心线程数量
this.maximumPoolSize = maximumPoolSize; //设置最大线程数量
this.workQueue = workQueue; //设置任务队列
this.keepAliveTime = unit.toNanos(keepAliveTime); //设置存活时间
this.threadFactory = threadFactory; //设置线程工厂
this.handler = handler; //设置拒绝策略
}
ThreadPoolExecutor有多个构造器,所有的构造器都会调用上面的核心构造器。通过核心构造器我们可以为线程池设置不同的参数,由此线程池也能表现出不同的特性。因此彻底搞懂这几个参数的含义能使我们更好的使用线程池,下面我们就来详细看一下这几个参数的含义。
- corePoolSize:核心线程数最大值,默认情况下新建线程池时并不创建线程,后续每接收一个任务就新建一个核心线程来处理,直到核心线程数达到corePoolSize。这时后面到来的任务都会被放到任务队列中等待。
- maximumPoolSize:总线程数最大值,当任务队列被放满了之后,将会新建非核心线程来处理后面到来的任务。当总的线程数达到
maximumPoolSize后,将不再继续创建线程,而是对后面的任务执行拒绝策略。 - workQueue:任务队列,当核心线程数达到
corePoolSize后,后面到来的任务都会被放到任务队列中,该任务队列是阻塞队列,工作线程可以通过定时或者阻塞方式从任务队列中获取任务。 - keepAliveTime:闲置线程存活时间,该参数默认情况下只在线程数大于
corePoolSize时起作用,闲置线程在任务队列上等待keepAliveTime时间后将会被终止,直到线程数减至corePoolSize。也可以通过设置allowCoreThreadTimeOut变量为true来使得keepAliveTime在任何时候都起作用,这时线程数最后会减至0。