从上篇文章中我们知道ThreadPoolExecutor继承于AbstractExecutorService,它是java并发的主要实现类,我们通常使用的三类线程池,都是通过ThreadPoolExecutor实现的。可以这样说,只要熟悉了ThreadPoolExecutor,那么对java线程池运作原理有了80%的掌握。
一、线程的执行
上一篇文章的UML图中可以看到,线程池的最上层接口Executor里面只有一个execute()方法,这个方法的具体实现就在ThreadPoolExecutor中看,我们从这里开始研究线程池的运行原理。首先来看源码
1. execute方法
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*下面有3块分支逻辑:
* 1.如果工作线程数小于核心线程数,则创建一个线程运行提交的任务
* 2.如果任务入队列成功,还要再次判断线程池是否在运行状态(存在一种可能是入队列过程中,线程池死掉了,或者线程池被shotdown了)。如果线程池不在运行状态,则拒绝这个人任务。如果线程池在运行状态,且线程数为0,则创建一个线程,去拉取队列中的任务执行。
* 3.如果入队列和创建新的线程都是失败了(创建新线程失败是因为线程数大于最大允许的线程数了),则拒绝这个任务
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
接下来是对上面出现的几个方法的深入解析,篇幅比较长,请结合开发工作上的源码,耐心阅读。
2. 线程池的几种状态
下面的状态可以不用管移位,理解状态是一位数就好了,比如RUNNING可以理解为-1,SHUTDOWN理解为0.
- RUNNING = -1 << 29。高30-32位为-1,编码为111,表示线程池在运行状态
- SHUTDOWN = 0 << 29。高30-32位为0,编码000,表示线程池处于正在关闭状态(当调用shutdown方法时为此状态),此时不再接受新任务,会执行之前已经提交的任务。
- STOP = 1<< 29。高30-32位为2,编码001,表示处于停止状态。会中断正在执行的任务,不再执行等待的任务,不再接受新任务。当调用shutdownNow方法时处于此状态。
- TIDYING = 2 << 29。高30-32位为2,编码010,表示处于正在终止状态,当工作线程为0,执行terminated前为此状态。
- TERMINATED = 3 << 29。高30-32位为3,编码011,表示线程池为终止状态,这是线程池最后的状态。表示线程池的工作线程为0,等待队列为0,不再接受新任务。
3. addWorker方法
addWorker方法可以看到线程是如何被创建和启动的。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
/*这里是判断是否还有需要创建新线程。如果状态是大于等于SHUTDOWN,并且满足下面三种条件任何一种则不再创建新的线程:
* 1.状态大于SHUTDOWN.
* 2.任务不为空。任务为空的话可能是为了创建新线程去抓取队列中的等待线程执行。
* 3.任务队列为空。如果任务队列都为空了,则没必要创建新线程了。
*/
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
// 如果工作线程数大于容量,或者工作线程大于核心线程数(大于最大线程数),则返回false,不创建线程
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 增加线程数,如果增加失败,则轮询内循环
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
// 再次检查状态,如果状态改变,则需要轮询外循环
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// Worker对象里面有重要的代码,工作线程长期存活的逻辑在这里面,后面讲解
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
// 这里加锁主要是下面要操作largestPoolSize和workers
mainLock.lock();
try {
// 首先检查线程池的状态
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 入工作线程
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
// 入工作队列成功才会启动工作线程
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
// 启动线程失败,则走失败的逻辑,做一下补处理,后面讲解
addWorkerFailed(w);
}
return workerStarted;
}
4. Worker对象
从下面代码可以看到,Worker继承了AQS,实现了Runnable,所以它既有了加锁的属性,也可以作为一个线程任务运行。对于Worker,了解下面几个方法就差不多了
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
Worker(Runnable firstTask) {
// 初始化的状态为-1
setState(-1);
this.firstTask = firstTask;
// 这里是线程工厂生成一个新的线程
this.thread = getThreadFactory().newThread(this);
}
// 这里是运行线程的入口,调用了外部类的runWorker方法,runWorker是重点,后面讲解
public void run() {
runWorker(this);
}
// 释放锁的时候会把状态置为0
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
}
其实看Worker对象也看不出线程池是如何运行的,得去研究runWorker方法,才能了解到里面的精髓。
5. runWorker方法
从runWorker方法中,我们就能基本明白线程池的运行奥妙了
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
// 解锁其实就是把状态设置为0
w.unlock();
boolean completedAbruptly = true;
try {
// 这个轮询就是工作线程一直会运行的奥妙所在了,它会不停的去任务队列中获取新任务,一旦获取到就执行,没有任务时它会阻塞或者直接退出
while (task != null || (task = getTask()) != null) {
w.lock();
// 这个if主要用来判断是否需要中断当前线程的。首先判断线程池的状态是否大于STOP,如果没有大于STOP,则检查工作线程是否被中断了,如果中断了则需要再次检查线程池状态是否大于STOP。最后再判断工作线程是否被清除了中断,如果被清除了,则需要恢复中断状态。
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
// 执行前做一些准备工作,默认是空方法,留给子类复写逻辑
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
// 执行后做一些收尾工作,默认是空方法,留给子类复写逻辑
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
// 退出做补处理,后面讲解
processWorkerExit(w, completedAbruptly);
}
}
线程的获取任务以及阻塞逻辑在getTask方法里面
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 如果线程池状态大于等于SHUTDOWN,并且队列为空(或者线程池状态大于等于STOP),则该工作线程退出,返回null任务
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// allowCoreThreadTimeOut默认是false,则判断工作线程数据是否大于核心线程数
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 下面满足两种条件工作线程会退出:第一工作线程数大于最大线程数,或者超时了;第二,在第一条件满足的前提下,工作线程数大于1,或者工作列队为空(为什么要判断工作线程数是否大于1,是因为要保留最少一个线程去消耗掉任务队列的等待执行的任务)
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// timed的值,如果工作线程数大于核心线程数的话为true,所以如果获取任务会调取poll方法,等待一定时间就返回。在三个普通线程池中固定线程池的的keepAliveTime是0,所以会立刻返回,如果没有获取的任务就会回到上一步的逻辑,判断是否退出。而缓存线程池的话,keepAliveTime为60秒,等待60秒后看是否获取到任务。
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
二、线程池的关闭
线程池的关闭方法有两种,分别是shutdown和shutdownNow。
1. shutdown方法
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 安全检查,是否有权限终止,暂时不研究,一般情况下都是有权限的
checkShutdownAccess();
// 把线程池状态设置为SHUTDOWN
advanceRunState(SHUTDOWN);
// 中断空闲的线程
interruptIdleWorkers();
// 钩子方法,ScheduledThreadPoolExecutor类中实现
onShutdown();
} finally {
mainLock.unlock();
}
// 尝试终止线程池,后面讲解
tryTerminate();
}
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
// 中断空闲线程的逻辑在下面这个方法,参数onlyOne表示是否只中断一个线程
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
// 首先需要加锁
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
// 第一判断是否已经中断了,第二尝试加锁,如果线程正在运行中的话,这个尝试加锁会失败,所以说它是中断空闲的线程。如果空闲的线程被中断的话,一般情况下会从阻塞中恢复过来,然后判断线程是否退出,从上文的分析中可以看出具体的逻辑。
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
final void tryTerminate() {
for (;;) {
int c = ctl.get();
// 满足下面三个条件如何一种,该方法会退出。第一,线程池在运行状态;第二,线程池的状态大于等于TIDYIING,说明线程池已经准备终止完毕,无须再调用终止方法。第三,线程池状态为SHUTDOWN,并且任务队列不为空,这时不能终止,应该等任务队列的任务被执行完毕再考虑终止。
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
// 这一步是考虑工作线程是否都退出了,如果没有退出完毕,则中断一个空闲的线程,让中断传播下去(注意线程退出也会调用tryTerminate方法,所以中断具有传播效应)
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
// 加锁,一般需要改变线程池状态之前都需要加锁
mainLock.lock();
try {
// 把线程池状态设置为TIDYING,然后再调用terminated方法
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
// 在这里是空方法
terminated();
} finally {
// 把线程池的状态设置TERMINATED,这是最终的状态了
ctl.set(ctlOf(TERMINATED, 0));
// 唤醒等待线程池终止的线程
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
}
}
2. shutdownNow方法
shutdownNow跟shutdown有稍微的区别,我们可以从源码中看到
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 状态设置为STOP
advanceRunState(STOP);
// 中断正在运行的线程
interruptWorkers();
// 清除掉所有等待队列的任务
tasks = drainQueue();
} finally {
mainLock.unlock();
}
// 尝试终止线程池,这上面分析过,不再赘述
tryTerminate();
return tasks;
}
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 遍历所有工作线程,然后中断已经启动的线程(因为创建工作线程的时候是先入workers队列,然后再启动线程的,所以需要判断线程是否启动)
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
void interruptIfStarted() {
Thread t;
// 线程状态大于等于0,说明是线程已经启动了
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
// 清除等待队列的逻辑其实就是调用BlockingQueue的drainTo方法,它会把队列里的所有元素一个个放到入参的集合里,然后清空队列
private List<Runnable> drainQueue() {
BlockingQueue<Runnable> q = workQueue;
ArrayList<Runnable> taskList = new ArrayList<Runnable>();
q.drainTo(taskList);
if (!q.isEmpty()) {
for (Runnable r : q.toArray(new Runnable[0])) {
if (q.remove(r))
taskList.add(r);
}
}
return taskList;
}
3. 两个关闭方法的区别
- shutdown中断空闲线程,任务队列的任务会被执行完。shutdownNow中断所有启动的线程,任务队列的任务会被清空,不再执行。
- shutdown设置线程池状态为SHUTDOWN,shutdownNow是STOP。
- shutdown返回空,shutdownNow返回等的执行的任务集合。
- 两者都会让线程池不再接受新任务。
三、工作线程的退出
从上文线程的执行模块分析中,我们留了一个processWorkerExit方法没讲解,这个方法在工作线程退出的时候会执行
// 这里有两个入参,第一个是工作对象,第二个代表线程是否在运行中异常退出了,可能是任务抛出了异常没有捕获
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 如果是异常退出,则工作线程数没有做减1
if (completedAbruptly)
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 统计线程池完成的任务数
completedTaskCount += w.completedTasks;
// 从工作线程队列中删除这个工作线程
workers.remove(w);
} finally {
mainLock.unlock();
}
// 尝试终止线程池,之前分析过
tryTerminate();
int c = ctl.get();
// 如果线程池状态小于STOP,则需要保证有足够的工作线程去执行任务队列的任务
if (runStateLessThan(c, STOP)) {
// 非异常退出需要考虑是否有足够的线程执行任务
if (!completedAbruptly) {
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
if (min == 0 && ! workQueue.isEmpty())
min = 1;
if (workerCountOf(c) >= min)
return; // replacement not needed
}
// 执行到这里说明两种情况,第一线程是异常退出的,第二工作线程数量不足了。
// 这时需要创建一个工作线程去执行任务。所以使用了线程池后,
// 我们不用担心线程异常退出导致线程池的核心线程数量减少了
addWorker(null, false);
}
}
写到这里,我联想到当初我们做项目的时候直接new Thread,一开始就固定线程的数量,后面不会再添加。当执行消息队列任务的时候,出现了线程数变少的问题,如果用线程池是不会出现这种情况了。就算线程池中的线程被莫名的退出,核心线程数也不会减少。
四、线程池的拒绝策略
线程池有4中拒绝策略:
- AbortPolicy。默认策略,直接抛出异常。这种策略很好地保护了服务器资源,避免任务队列满了还在同步执行任务
- CallerRunsPolicy:同步执行,在提交任务的线程中执行run方法。
- DiscardPolicy:抛弃策略。就是啥都不做,直接放弃掉提交的任务。
- DiscardOldestPolicy:抛弃最老策略。抛弃队列中的最老的元素,并提交新任务(e.execute(t))
五、总结
至此我们可以说掌握了线程池原理的80%,还剩下的20%需要从它的子类中、以及阻塞队列中去做补充了。