接着上一篇文章,知道线程池的一些相关概念后,一起来看看实现原理吧。
本文讲述ThreadPoolExecutor源码,力求理清执行顺序,尽量保持思路清晰,请耐心看完~
文章导读
Worker表示线程池中的每一个任务,与线程一一对应。是AQS的子类,实现其独占模式,封装一些了对于资源操作的方法。
1.1 基本属性
重要的是thread(当前worker线程),firstTask(初始任务),completedTasks(任务计数器)。
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/**
* 这个类永远不会被序列化,设置serialVersionUID
* 是为了停止javac编译器的警告
*/
private static final long serialVersionUID = 6138294804551838833L;
//表示一个工作线程,null则说明线程工厂创建出错了
final Thread thread;
//需要运行的初始任务,可能为空
Runnable firstTask;
//每个线程的任务计数器,表示完成的任务数量
volatile long completedTasks;
......
}
1.2 构造方法
Worker在第一次接收任务的时候被线程工厂创建,其中成员变量thread就是基于Worker的线程。
Worker(Runnable firstTask) {
//设置AQS.state为-1表示在运行之前禁止被中断
setState(-1);
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
1.3 对AQS相关方法的实现
Worker既然继承了AbstractQueuedSynchronizer,就一定会有相关钩子方法的实现。钩子方法是isHeldExclusively(),tryAcquire(int unused),tryRelease(int unused)。而lock(),tryLock(),unlock(),isLocked()都是对他们的进一步封装,非常的简练。如果有兴趣可以回忆回忆ReentrantLock都是怎么实现的,比较一下区别。
state表示当前线程的运行状态,总共有3种情况:
//是否持有独占锁,根据state判断
//state 0:表示锁没有被任何线程获取
//state 1:表示锁已经被占有
protected boolean isHeldExclusively() {
return getState() != 0;
}
//尝试获取锁,成功后设置当前线程为锁的持有者
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
//释放锁
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
//会先调用tryAcquire(1),若拿不到锁则阻塞获取锁资源
public void lock() { acquire(1); }
//尝试获取锁,不会阻塞
public boolean tryLock() { return tryAcquire(1); }
//会先调用tryRelease(1),若释放成功则去等待队列从队尾向前找下一个需要唤醒的节点
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
1.4 对线程的中断方法
interruptIfStarted():用于中断工作线程,保证要中断的thread必须是已经初始化完成的,而且已经运行了。需要注意的是,Worker的构造方法中将state设置为-1。
void interruptIfStarted() {
Thread t;
//确保线程已经运行并且中断标志为还是false时,就执行中断操作。
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
execute(Runnable command):总共分为3个步骤。
1)如果工作线程数<核心线程数,则每次有新任务来,都创建一个新的线程来处理。调用addWorker()自动检查线程池状态和工作线程数,可以防止一些错误,比如不该创建线程的时候创建线程。
2)当任务成功入队后,我们仍然需要双重检查机制(double-check),检查是否真的需要 添加一个线程。因为某个线程挂了就需要检查,或者进入这个方法后线程池已经关闭了。所以需要再次检查 线程的状态,如果线程池关闭了就有必要回滚进入阻塞队列,或没有线程时启动一个新的线程。
3)如果无法将任务入队,就尝试创建一个线程。如果创建失败了,就表示线程池已经关闭或饱和了 ,就执行拒绝策略。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
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();
//1.如果线程池不是RUNNING状态,且成功从阻塞队列中删除任务,则该任务由当前 RejectedExecutionHandler 处理。
if (! isRunning(recheck) && remove(command))
reject(command);
// 2.否则如果线程池中运行的线程数量为0,则通过addWorker(null, false)尝试新建一个线程,
//新建线程对应的任务为null。
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
//添加任务失败,执行拒绝策略
reject(command);
}
addWorker(Runnable firstTask, boolean core):创建工作线程,成功则返回true,并启动线程;失败则返回false,并从工作线程集合中删除。方法比较长,我把主要过程先理一下:
1)首先判断线程池的状态,如果已经处于非运行状态,就看是否满足关闭状态或任务为空或阻塞队列为空。即创建失败。
2)将当前工作线程与核心线程数或最大线程数比较,如果当前线程数比较大,就创建失败。
3)加互斥锁,再次判断异常情况,若出现线程池状态异常的情况,则添加失败,从工作线程集合中移除 。
4)确定无误后创建新的工作线程,添加成功后就启动线程。
再次回顾一下线程池的状态:
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 当线程状态为非运行状态,并且满足关闭状态或任务为空或阻塞队列为空时
//工作线程创建失败,返回false
if (rs >= SHUTDOWN &&!(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
//当core==true,会将工作线程数与核心线程数比较;
//当core==false,将工作线程数与最大线程数比较
//当前线程数大于等于对应的工作线程数或核心线程数,直接返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//如果CAS操作新增工作线程数成功了,就跳出外面的循环
//失败了则继续循环,自旋尝试
if (compareAndIncrementWorkerCount(c))
break retry;
//刷新ctl
c = ctl.get();
//当前线程池状态与之前获取的不一样则说明状态改变了
//跳回去刷新线程池状态,和之前的一系列关于创建的判断
if (runStateOf(c) != rs)
continue retry;
}
}
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;
mainLock.lock();
try {
//重新检查,检查是否线程工厂创建失败或线程池在获取锁之前关闭了
int rs = runStateOf(ctl.get());
//当线程池处于运行状态或者处于关闭状态且任务为空
//就将当前worker添加进去
if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {
//再次检查线程是否已启动
if (t.isAlive())
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;
}
这两个方法整体概括一下,可以得到线程池对于工作线程数量的控制策略的一个策略:
如果工作线程数 < 核心线程数,则创建新的线程处理请求。
由Worker的构造方法可以知道,创建的thread传入了this,就是当前的Worker,所以thread.run()实际上调用的就是Worker.run()。
runWorker():Worker.run()会调用该方法,执行相应的的任务。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
//获取firstTask
Runnable task = w.firstTask;
//清除firstTask
w.firstTask = null;
//该方法是addWorker()的一个分支,所以需要释放锁资源。
w.unlock();
//检测线程是否异常结束的一个标志
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
//如果线程池已经停止了,或中断标志为false则中断当前线程
//如果线程池处于运行状态,则清除中断标志位
if ((runStateAtLeast(ctl.get(), STOP)||(Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
//执行任务前处理
beforeExecute(wt, task);
Throwable thrown = null;
try {
//让当前线程执行任务,震惊,竟然直接就调用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退出
processWorkerExit(w, completedAbruptly);
}
}
getTask():获取任务,出现以下情况会导致返回空,worker退出。
1)当前线程数超过了最大线程数
2)线程池处于stop状态
3)线程池处于shutdown状态并且阻塞队列为空
4)当前worker获取任务等待超时(超过keepalive的时间)。
private Runnable getTask() {
boolean timedOut = false;
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 如果线程池处于非运行状态
//或者如果处于SHUTDOWN状态且阻塞队列为空
//则减少工作线程数且返回空
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
//工作线程数是否超过核心线程数
//是否执行定时获取任务标记
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//满足以下任意一个条件则返回空,worker退出
//1)工作线程数超过最大线程数
//2)获取任务超时并且阻塞队列为空或工作线程数超过1个
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//如果没设置空闲时间,就直接从阻塞队列中获取任务
//(workQueue.take()是阻塞方法),拿到任务返回,线程被重用
//如果设置了空闲时间,就在keepAliveTime时间内拿到任务
//拿不到就返回空
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
processWorkerExit():将worker从工作队列中移除,再看看有没有必要加入新的worker。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly)
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
//移除worker
workers.remove(w);
} finally {
mainLock.unlock();
}
//尝试终止线程池
tryTerminate();
int c = ctl.get();
//如果线程池的状态是RUNNING或SHUTDOWN,
//则寻找新的worker,代替死亡的worker
if (runStateLessThan(c, STOP)) {
//线程正常终止,completedAbruptly=false是线程异常结束
if (!completedAbruptly) {
//找到最小worker的数量
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
//阻塞队列不为空,但最小worker数为0时,需要保留一个
if (min == 0 && ! workQueue.isEmpty())
min = 1;
//worker数量多,再c黄健
if (workerCountOf(c) >= min)
return;
}
addWorker(null, false);
}
}
将提交任务,执行任务方法整体概括一下,可以得到线程池对于工作线程数量的控制策略:
1)如果工作线程数 < 核心线程数,则创建新的线程处理请求。
2)如果核心线程数 < 工作线程数 < 最大线程数,则将任务放入阻塞队列,当阻塞队列满的时候才创建新的线程。
线程池的关闭方法由
4.1 tryTerminate()--尝试关闭,TIDYING->TERMINATED
tryTerminate():执行线程池的很多操作时都会调用这个tryTerminate(),能够中断空闲的线程。
final void tryTerminate() {
for (;;) {
int c = ctl.get();
//线程池处于TIDYING,TERMINATED或
//处于关闭状态且阻塞队列为空,说明线程池正在终止或不需要终止,不用管。
if (isRunning(c) || runStateAtLeast(c, TIDYING) ||(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
//当线程数不为0时,中断一个空闲线程
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//线程池处于TIDYING状态
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
//钩子方法,转换为终止状态
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
//唤醒所有等待在终止condition上的线程
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
}
}
interruptIdleWorkers(boolean onlyOne):中断一个空闲线程,onlyOne为true时,中断一个可能等待任务的线程。如果阻塞队列为空,有的worker就会因为获取task而被阻塞,此时中断那些被阻塞的线程。onlyOne为false时,中断所有空闲线程。
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
//tryLock()能拿到锁,说明此时的worker已经完成任务,成为空闲线程或是被LockSuport.park()阻塞的线程
if (!t.isInterrupted() && w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
4.2 shutdown()--优雅关闭,RUNNING->SHUTDOWN
shutdown():线程池转换为SHUTDOWN状态,等阻塞队列任务执行完,再转换为TIDYING。
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//检查每一个线程池的线程是否有可以ShutDown的权限
checkShutdownAccess();
//更改为SHUTDOWN状态
advanceRunState(SHUTDOWN);
//中断所有空闲线程
interruptIdleWorkers();
//用于ScheduledThreadPoolExecutor取消延时任务
onShutdown();
} finally {
mainLock.unlock();
}
tryTerminate();
}
4.3 shutdownNow()--直接关闭,RUNNING->STOP
shutdownNow():线程池转换为STOP状态,抛弃阻塞队列的任务。
public List shutdownNow() {
List tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
//直接转换为STOP
advanceRunState(STOP);
//中断所有线程
interruptWorkers();
//剩余没有执行完的任务将被返回
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
最后,写完已经很不容易了.......大部分的方法都已经涵盖了,线程池设计的还是很严谨的,一次又一次的判断,CAS操作,步步为营。
回答一下上次留的问题,如何设计一个线程池?
首先是线程池的创建,设置核心线程数,最大线程数,空闲时间,阻塞队列,拒绝策略等。其次理清楚执行任务的流程,设置Worker作为每一个线程的代表,完善添加,执行任务方法,接收任务时,得有一系列的安全判断,判断工作线程数,判断池的状态,阻塞队列状况。还要有具体的控制工作线程数的策略,比如小于核心线程数则直接创建worker,执行任务。大于核心线程数小于最大线程数时将任务方法阻塞队列,当阻塞队列满时再创建新的线程。直到创建不了了就执行拒绝策略。每一步操作都要对线程池的状态判定,保证线程安全的,中间可以穿插着tryTerminated,或者时不时的把空闲的线程给中断了。
当发出关闭命令时,可以加互斥锁,改变线程池的状态,此时拒绝新任务,等待任务执行完毕,自旋的方式来中断。
文章若有不当之处,欢迎评论指出~
如果喜欢我的文章,欢迎关注我的知乎专栏Java修仙道路~