线程池的状态
首先我们来看看ThreadPoolExecutor类中定义的部分变量:
volatile int runState; //volatile 是一个类型修饰符。
//volatile 的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略。
static final int RUNNING = 0;
static final int SHUTDOWN = 1;
static final int STOP = 2;
static final int TERMINATED = 3;
runState:当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性。
下面的几个static final变量表示runState可能的几个取值。
1、当创建线程池后,初始时,线程池处于RUNNING状态;
2、如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
3、如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
4、当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
ThreadPoolExecutor类的部分成员变量
我们再来看一下ThreadPoolExecutor类中其他的一些比较重要成员变量:
private final BlockingQueue<Runnable> workQueue; //任务缓存队列,用来存放等待执行的任务
private final ReentrantLock mainLock = new ReentrantLock(); //线程池的主要状态锁,对线程池
//状态(比如线程池大小、runState等)
//的改变都要使用这个锁
private final HashSet<Worker> workers = new HashSet<Worker>(); //用来存放工作集
private volatile long keepAliveTime; //线程存货时间
private volatile boolean allowCoreThreadTimeOut; //是否允许为核心线程设置存活时间
private volatile int corePoolSize; //线程池的大小(即线程池中的线程数目大于这个参数时
//,提交的任务会被放进任务缓存队列)
private volatile int maximumPoolSize; //线程池最大能容忍的线程数
private volatile int poolSize; //线程池中当前的线程数
private volatile RejectedExecutionHandler handler; //任务拒绝策略
private volatile ThreadFactory threadFactory; //线程工厂,用来创建线程
private int largestPoolSize; //用来记录线程池中曾经出现过的最大线程数
private long completedTaskCount; //用来记录已经执行完毕的任务个数
任务从提交到最终执行完毕经历的过程
在ThreadPoolExecutor类中,最核心的任务提交方法是execute()方法,虽然通过submit()也可以提交任务,但是实际上submit()方法里面最终调用的还是execute()方法,所以我们只需要研究execute()方法的实现原理即可,那么我们就来看看execute()方法的源码:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException(); //判断提交的任务command是否为null,
//若是null,则抛出空指针异常。
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command); //进行应急处理,从名字可以看出是保证
//添加到任务缓存队列中的任务得到处理。
}
else if (!addIfUnderMaximumPoolSize(command))
reject(command);
}
}
这上面的有些代码话是比较难懂的,我们现在一句一句解释:
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command))
这是一个或运算,首先会判断poolSize和corePoolSize的关系,如果线程池中的线程数大于等于线程池的大小,那么就直接进入if语句内部;如果线程池中的线程数小于线程池的大小,那么在执行addIfUnderCorePoolSize(command)方法,如果返回的结果为true,那么不进入if语句内部,如果返回的结果为false,那么进入if语句内部。
if (runState == RUNNING && workQueue.offer(command))
```
else if (!addIfUnderMaximumPoolSize(command))
reject(command);
}
首先判断线程池的状态是不是RUNNING,如果不是,那么直接去判断下一个else if,然后再去执行addIfUnderMaximumPoolSize(command),如果返回值为true,则跳出整个if体,如果返回值为false,那么执行reject(command);
如果是,则将此任务放入任务缓存队列,但是如果执行offer()失败,那么也会直接去判断下一个else if。
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
这也是一个或运算,首先判断线程池的状态是不是RUNNING,如果不是,再去判断线程池中当前的线程数是不是等于0,如果是,那么执行ensureQueuedTaskHandled(command)方法;不然的话,跳出当前的if体。
解释到现在还有2个方法没有解释,分别是addIfUnderCorePoolSize(command)和addIfUnderMaximumPoolSize(command)。那我们再来看看这两个方法的源码。
addIfUnderCorePoolSize(command):
private boolean addIfUnderCorePoolSize(Runnable firstTask) {
Thread t = null;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (poolSize < corePoolSize && runState == RUNNING)
t = addThread(firstTask); //创建线程去执行firstTask任务
} finally {
mainLock.unlock();
}
if (t == null)
return false;
t.start();
return true;
}
整个方法的整体流程就是,首先获取到锁,因为这地方涉及到线程池状态的变化,然后在判断线程池中当前的线程数和线程池的大小以及线程池的状态,当一切都ok时,执行addThread()方法,该方法的返回值是一个Thread类型的,如果创建线程失败,则t==null;如果创建线程成功,那么就让该线程start()。
我们再来看看addThread()方法:
private Thread addThread(Runnable firstTask) {
Worker w = new Worker(firstTask);
Thread t = threadFactory.newThread(w); //创建一个线程,执行任务
if (t != null) {
w.thread = t; //将创建的线程的引用赋值为w的成员变量
workers.add(w);
int nt = ++poolSize; //当前线程数加1
if (nt > largestPoolSize)
largestPoolSize = nt;
}
return t;
}
这个方法相对来说比较容易理解,那我们再来看看worker类的源码:
private final class Worker implements Runnable {
private final ReentrantLock runLock = new ReentrantLock();
private Runnable firstTask;
volatile long completedTasks;
Thread thread;
Worker(Runnable firstTask) {
this.firstTask = firstTask;
}
boolean isActive() {
return runLock.isLocked();
}
void interruptIfIdle() {
final ReentrantLock runLock = this.runLock;
if (runLock.tryLock()) {
try {
if (thread != Thread.currentThread())
thread.interrupt();
} finally {
runLock.unlock();
}
}
}
void interruptNow() {
thread.interrupt();
}
private void runTask(Runnable task) {
final ReentrantLock runLock = this.runLock;
runLock.lock();
try {
if (runState < STOP &&
Thread.interrupted() &&
runState >= STOP)
boolean ran = false;
beforeExecute(thread, task); //beforeExecute方法是ThreadPoolExecutor类的
//一个方法,没有具体实现
try {
task.run();
ran = true;
afterExecute(task, null);
++completedTasks;
} catch (RuntimeException ex) {
if (!ran)
afterExecute(task, ex);
throw ex;
}
} finally {
runLock.unlock();
}
}
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this); //当任务队列中没有任务时,进行清理工作
}
}
}
很多人会问了我们看这个类的源码有什么意义,好像和上面的联系不大,仅仅用到了一个构造方法而已,但是这个类是实现了Runnable接口,所以我们要重点看看这个类的run()方法。
它首先执行的是通过构造器传进来的任务firstTask,在调用runTask()执行完firstTask之后,在while循环里面不断通过getTask()去任务缓存队列中取新的任务来执行。
这是一个非常巧妙的设计方式,假如我们来设计线程池,可能会有一个任务分派线程,当发现有线程空闲时,就从任务缓存队列中取一个任务交给空闲线程执行。但是在这里,并没有采用这样的方式,因为这样会要额外地对任务分派线程进行管理,无形地会增加难度和复杂度,这里直接让执行完任务的线程去任务缓存队列里面取任务来执行。
addIfUnderMaximumPoolSize(command)
private boolean addIfUnderMaximumPoolSize(Runnable firstTask) {
Thread t = null;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (poolSize < maximumPoolSize && runState == RUNNING)
t = addThread(firstTask);
} finally {
mainLock.unlock();
}
if (t == null)
return false;
t.start();
return true;
}
这个方法和上一个方法差不多,唯一的区别在于if条件中线程池中当前的线程数和线程池最大能容忍的线程数比较,而不是和线程池的大小比较。
综上所述,我们来总结一下:
- 如果当前线程池中的线程数目小于corePoolSize(线程池的大小),则每来一个任务,就会创建一个线程去执行这个任务;
- 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
- 如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
- 如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。
最后,为了便于各位读者理解,我用一个比较现实的例子来打个比方:
一个工厂有10个工人,每个工人同时只能做一件任务。因此只要当10个工人中有工人是空闲的,来了任务就分配给空闲的工人做;当10个工人都有任务在做时,如果还来了任务,就把任务进行排队等待;如果说新任务数目增长的速度远远大于工人做任务的速度,那么此时工厂主管可能会想补救措施,比如重新招4个临时工人进来;然后就将任务也分配给这4个临时工人做;如果说着14个工人做任务的速度还是不够,此时工厂主管可能就要考虑不再接收新的任务或者抛弃前面的一些任务了。当这14个工人当中有人空闲时,而新任务增长的速度又比较缓慢,工厂主管可能就考虑辞掉4个临时工了,只保持原来的10个工人,毕竟请额外的工人是要花钱的。
这里10表示corePoolSize,而14表示maximumPoolSize。