概述
我们知道Doug Lea为了方便我们开发,在Executors类里提供了很多方法方便我们创建不同类型的线程,但阿里开发规范又强制禁止使用newCachedThreadPool等方法,原因是Executors里的线程池要么是无界队列,要么是无限线程,任务瞬间爆发到来的时候,会造成OOM。所以阿里要求开发人员使用ThreadPoolExecutor构造方法创建线程池,那么我们就需要了解ThreadPoolExecutor类的内部。
类结构
public class ThreadPoolExecutor extends AbstractExecutorService {
//一个原子变量表示两个东西: 高三位表示线程池的状态 低29位表示线程池中 线程数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//Integer的bit数, 所以COUNT_BITS = 29
private static final int COUNT_BITS = Integer.SIZE - 3;
//线程池的线程最大容量 用位数表示,不用担心二进制运算,这里只用了与、或、取反
//一起回顾一下:与: 任何数和0与都是0,和1与为原数的值, 或: 任何数和1或都是1,和0或为原数的值, 最高位为1表示负数,最高位为0表示正 取反:所有位0变1,1变0就行
//CAPACITY=0001 1111 1111 1111 1111 1111 1111 1111 (高位3个0,低位29个1)
//大家都懂,计算过程只说这一个吧,先把1左移29位低位补0,就变成了001后面29个0,再减1,就变成了高位是000后面29个1
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
//线程池的状态:
//1110 0000 0000 0000 0000 0000 0000 0000
//运行状态:能接受新任务,队列中的任务可继续运行
private static final int RUNNING = -1 << COUNT_BITS;
//0000 0000 0000 0000 0000 0000 0000 0000
//关闭状态:不再接受新任务,队列中的任务仍可继续执行
private static final int SHUTDOWN = 0 << COUNT_BITS;
//0010 0000 0000 0000 0000 0000 0000 0000
//不再接受新任务,不再执行队列中的任务,中断所有执行中的任务(发中断消息,虽然发了中断消息,正在运行的中不中断是自己的事了哦)
private static final int STOP = 1 << COUNT_BITS;
//0100 0000 0000 0000 0000 0000 0000 0000
//所有任务均已终止,workerCount的值为0,转到TIDYING状态的线程即将要执行terminated()钩子方法.
private static final int TIDYING = 2 << COUNT_BITS;
//0110 0000 0000 0000 0000 0000 0000 0000
//terminated()方法执行结束
private static final int TERMINATED = 3 << COUNT_BITS;
//得到线程池的状态 ctl 和 CAPACITY 取反后 进行与操作, 就只有高三位保留原值,即线程池的状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
//得到线程的数量 ctl和CAPACITY与,就把高3位变为了0,低29位保留原值,就线程的数量
private static int workerCountOf(int c) { return c & CAPACITY; }
//线程池状态和工作线程数量取或, 得到ctl值
private static int ctlOf(int rs, int wc) { return rs | wc; }
。。。
}
上面一大段,其实就是说线程池状态和工作线程数量,因为这段比较重要我们再回顾一次。
- ctl:原子的Integer类,高三位表示线程池的状态,低29位表示工作线程数量,又get一个技能,怎么保证两个变量的原子性?把两个变量变成一个,也是一种很奇妙的方法哦
- 线程池状态:可以看到,只有在线程池Running时处理任务,或者SHUTDOWN状态但等待队列又不为空时处理等待队列中的任务,这两组条件很重要,后面会有很多地方做这个判断,一定记住哈,起个名字:执行任务状态
构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- corePoolSize: 核心线程池的数量,当任务进来以后,如果工作线程数量没有达到这个值,就新建一个线程处理,默认情况下工作线程达到这个值后维持这个值(但allowCoreThreadTimeOut这个属性设置后,工作线程也会超时回收)
- workQueue: 任务队列(是一种阻塞队列,想了解的可以看前面阻塞队列的文章),当任务进来,工作线程达到核心线程(corePoolSize)数后,进入workQueue等待队列,进行等待。
- maximumPoolSize: 最大线程数量,我们知道阻塞队列可以是有界的,当队列满了以后怎么处理?寻找临时工,即额外开辟线程帮忙处理workQueue中的任务。maximumPoolSize就是最大允许的线程数。
- handler: 那如果已经最大线程了还是处理不过来,workQueue还是满的,怎么办?罢工不干了,RejectedExecutionHandler即拒绝策略,包括以下几种:抛出异常、直接丢弃、丢弃队列中最老的、调用者自己处理。 默认是抛出异常的拒绝策略。
- keepAliveTime: 我们再想下,既然maximumPoolSize里有临时工,如果临时工干完了活,是不是应该走了,什么时候走?keepAliveTime表示,如果等这么长时间,线程还是等不到任务,就退出,直到维持在核心数量
- threadFactory:线程工厂,顾名思义用来创建线程的,除了特殊情况,一般我们使用默认的线程工厂。
默认工厂类
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
//可以看到设置了统一格式的线程名,设为非守护线程,设置普通优先级
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
execute方法 提交任务
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* 处理的3步:上面构造方法中也说了
* 1. 当任务提交进来以后,先检查工作线程,是否小于核心线程数,是就直接起线程执行任务
* 2. 工作线程数不小于核心线程数,进行入队
* 3. 如果入队失败,就新建线程执行,再失败就执行拒绝策略了
*/
//ctl 刚才说过表示线程池状态和工作线程数
int c = ctl.get();
//如果工作线程数小于核心线程数
if (workerCountOf(c) < corePoolSize) {
//addWorker有两个参数command是待执行的任务,core是表示是否核心线程
if (addWorker(command, true))
return;
c = ctl.get();
}
//到了这里,肯定没有新建核心线程执行任务成功,不然上面就return了
//再次判断线程池状态,是否在运行,是的话,将任务入队
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//再次判断线程池状态,是否在运行,不是的话,将任务移除,并执行拒绝策略
//这两个if判断说明了,不是running状态的线程池不再接收新任务处理
if (! isRunning(recheck) && remove(command))
reject(command);
//这里是个拖底,如果线程池中没有线程执行任务了,新增线程处理任务
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//添加非核心线程执行任务,失败则执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
方法中多次出现addWorker方法,但每次的参数都不同,我们具体看addWorker方法
/**
* firstTask表示有需要直接处理的任务
* core 表示是否新建核心线程
*/
private boolean addWorker(Runnable firstTask, boolean core) {
//retry标签,Java 标签 开发中很少用,类似与goto,调到指定的代码出执行
retry:
//有两重for循环,因为创建的过程中有可能线程池状态变了,会再次检查
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 这里就是上面提到的,执行任务状态(线程池是运行状态或者 SHUTDOWN状态且队列不为空)的反面
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
//如果线程池是执行任务的状态
for (;;) {
int wc = workerCountOf(c);
//新建核心线程core =true 则和corePoolSize比较,否则和maximumPoolSize比较,满了则返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//CAS操作ctl,成功则线程数量增加成功, 失败则要判断是状态变了还是线程数量变了
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) {
//这里进行加锁操作,不再允许其他线程修改线程池状态和操作workers
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
//再次判断了线程池执行任务的状态 小于SHUTDOWN的状态肯定是running
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();
}
//如果工作线程增加成功,则启动worker中的线程
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
//新增工作线程的钩子函数,工作线程集合移除新增的工作线程,回滚工作线程数,进行tryTerminate
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
上面分析了addWorker的过程,我们看到新增Worker对象并将Worker中的线程启动成功,才返回true,否则都返回失败。接下来我们一起看看Worker类。
//这个类比较有意思,即是AQS 阻塞队列,又是Runnable
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
private static final long serialVersionUID = 6138294804551838833L;
//线程池中的线程
final Thread thread;
//待执行的第一个任务,即待执行的Runnable,可能为空(当新建线程处理队列中的任务时)
Runnable firstTask;
//线程完成的任务数,仅做记录
volatile long completedTasks;
//构造方法将待执行的任务赋值给firstTask,并新建了一个线程
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
//thread构造方法传入的Worker对象本身,所以thread start执行的是worker的run方法
public void run() {
runWorker(this);
}
。。。
}
我们看到Worker有一个thread属性,构造方法中用自身this新建了一个线程赋值给thread,所以当上面addWorker方法中,启动worker的thread调用start后,会执行Worker的run方法,run方法调用runWorker方法。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
//还记得Worker初始化的时候state是-1,不允许interrupts,直到这里unlock设置0,允许往下执行了才允许中断
w.unlock(); // allow interrupts
//是否异常的完成
boolean completedAbruptly = true;
try {
//循环,直到getTask等于空时跳出循环,Worker的线程执行完成,退出(这里线程的生命周期结束了)
while (task != null || (task = getTask()) != null) {
w.lock();
// 线程池是停止状态时,将work的线程执行interrupt
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方法
// 现在整个就串起来了,线程池新建Worker对象,Worker对象新建了一个线程
// 然后执行线程的start,线程的start又会执行worker的run方法,worker的run方法又会执行task的run方法
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 {
//worker退出后的处理 记录completedTaskCount workers.remove(w)
//完成后如果线程池不是停止状态,会至少保留一个线程处理完所有等待的任务
processWorkerExit(w, completedAbruptly);
}
}
上面可以看到runWorker会不停的获取任务并执行,直到getTask方法返回null时,Worker退出,Worker中的线程到达终止态。我们在一起看下getTask方法。
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
//获取等待队列中的任务
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 又一次判断 执行任务状态, 不满足则返回空
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
//得到工作线程的数量
int wc = workerCountOf(c);
// 是否超时等待(当允许核心线程超时或者工作线程的数量大于核心线程数时进行超时等待)
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//如果(工作线程数大于最大线程数 或者允许超时) 而且 (工作线程池大于1或者队列为空)
//简单理解,超时或者队列是空,返回null
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
//CAS将工作线程数减1
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//根据timed的值决定是否需要超时等待
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
//r不为空说明正常拿到了值,正常返回
if (r != null)
return r;
//这里说明超时了
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
上面可以看到,只有在以下几种情况下getTask才返回null
- 线程池为不可执行任务状态了,不是running状态,也不是SHUTDOWN且任务队列不为空的状态(还记得我们上面起的名字吗?执行任务状态,该类多次判断这个状态,作者应该封装成方法的)
- 线程数量大于核心线程数,进行workQueue.poll的超时等待获取,获取为空,这时也会返回null,线程超时回收(是不是构造方法中的属性对上了)
- 线程数量不大于核心线程数,但核心线程允许超时回收,进行workQueue.poll的超时等待获取,获取为空,这时也会返回null,线程超时回收(是不是allowCoreThreadTimeOut属性也对上了)
小结
上面分析了ThreadPoolExecutor的构造方法,execute方法,了解任务被提交后处理的三个步骤,了解线程怎么创建,怎么和任务关联,又是何时回收的,明确了各属性在何时起作用。
下面我们一起看看shutDown和shutDownNow方法
shutDown 方法
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//检查是否有权限shutDown
checkShutdownAccess();
//CAS 设置SHUTDOWN状态
advanceRunState(SHUTDOWN);
//中断不忙碌的工作线程,即等待任务的线程
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
shutdownNow 方法
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//检查权限
checkShutdownAccess();
//CAS设置STOP状态
advanceRunState(STOP);
//中断不忙碌的工作线程
interruptWorkers();
//移除等待队列里的任务
tasks = drainQueue();
} finally {
mainLock.unlock();
}
//尝试终止线程池
tryTerminate();
return tasks;
}
可以看到shutdownNow方法比shutDown多了一步处理,drainQueue将等待队列中的任务全部移除。
tryTerminate方法
//尝试终止线程池
final void tryTerminate() {
for (;;) {
int c = ctl.get();
//又一次判断执行任务状态
//如果是执行任务状态或者已经触达过TIDYING状态,直接返回
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
//如果是非执行任务状态且工作线程不为空,尝试中断一个可中断线程
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//CAS替换TIDYING状态,成功则运行terminated钩子函数
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}