这次来看看worker线程启动和运行的相关方法。了解任务获取与异常处理机制。
关于线程池参数解释,启动方法解析请移步:
https://blog.csdn.net/xiaoyuchenCSDN/article/details/83549068
Worker
先说说Worker类,它作为线程的承载类、任务的执行者,自然少不了实现Rannable接口,在ThreadPoolExecutor的addWorker被执行后会通过Thread中的start方法开启worker线程,而内部run方法则会调用ThreadPoolExecutor的runWorker方法来执行任务。
不仅如此,worker还继承了AbstractQueuedSynchronizer(AQS)类,能够使用非重入锁。不同于ReentrantLock,AQS在保证了多个线程并发安全的同时,还拒绝了同一个线程的多次重入。使用AQS有一下两点原因:
- 当worker被加锁,则表示worker线程正在执行任务,该线程不应该被中断,且一个worker线程不应该同时执行两个任务。
- 线程池在执行shutdown方法或tryTerminate方法时会调用interruptIdleWorkers方法来中断空闲的线程,interruptIdleWorkers方法会使用tryLock方法来判断线程池中的线程是否是空闲状态(setCorePoolSize等方法也通过trylock来判断)。如果worker正在执行任务则其已经被加锁,此时trylock方法不能成功,进程不会被中断。保证了shutdown不会杀死有任务的worker线程。
注意:加锁为1;解锁为0;初始值为-1表示尚未启动
以下就是Worker代码
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
private static final long serialVersionUID = 6138294804551838833L;
//成功创建worker,thread不可能为空
final Thread thread;
//起始任务,可能为空
Runnable firstTask;
//记录完成任务个数
volatile long completedTasks;
//初始化方法,state为0,新建线程。
Worker(Runnable firstTask) {
setState(-1); // 防止启动之前就中断worker中的线程
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
//正式启动,在runWorker方法中循环读取任务。
public void run() {
runWorker(this);
}
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;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) { //getState>=0,0就是解锁,1就是加锁
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
runWorker
worker被调用start方法后,线程变为启动状态,之后线程启动后run方法会调用runWorker。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
//设置worker变为解锁状态
w.unlock();
//任务未被完整执行,出现异常
boolean completedAbruptly = true;
try {
//任务不为空或者任务队列中任务不为空,则开始执行任务。
//添加worker时可能添加没有task任务的worker
while (task != null || (task = getTask()) != null) {
//根据每一个worker开启AQS锁
w.lock();
//下面的逻辑是‘worker线程中断逻辑’,具体逻辑解释将会在代码下面找到
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task); //空方法
Throwable thrown = null;
try {
//会由于任务抛出异常而导致worker关闭。会在下边processWorkerExit方法重新创建。
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);
}
}
-
‘worker线程中断逻辑’解释:
其根本原因还是shutdown和stop状态变更的问题。
运行到此处说明已经获取了任务。如果此时状态为stop或级别更高(如tidying),worker线程应该被中断;而如果此时是shutdown状态则worker不能停止运行。
所以,stop状态会进入if方法体,执行interrupt方法;而shutdown状态不仅不会进入方法体,还会被interrupted方法清空线程中的中断状态,保证线程在执行任务时不会由于阻塞而退出任务。(这部分线程中断退出常识,请自行查阅)
至于多次使用runStateAtLeast方法进行判断,实际上保证了worker状态和线程状态的一致性,shutdownNow方法会先改变worker状态,后改变线程状态。 -
processWorkerExit方法调用场景:
1)终止线程池时,用于从线程集合中移除工作线程;
2)当前线程因为异常而退出,重新创建线程替换之;
3)线程池中因为设置allowCoreThreadTimeOut=true,导致工作线程全部被回收时,任务队列仍然有任务,则新建线程;
可能还有其他情况,此方法会在后边继续分析。
getTask
从队列里获取任务的方法,如果任务队列为空,会在此处进行阻塞
private Runnable getTask() {
boolean timedOut = false; // 上次是否超时
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//1 线程池状态是stop或以后。2 状态为shutdown且队列为空。
//在以上两个情况下,worker数量减一后推出
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
//是否进行超时控制,超过核心线程池数量之后,或者,允许核心线程超时,都一定会开启
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//超时控制被触发,会worker数量减一,返回空;触发失败从新循环判断。
//触发标准,(1||2)&&(3||4)
//1 worker数量大于最大线程池数量
//2 进行超时控制并且上次获取任务发生了超时
//3 worker数量大于1
//4 任务队列为空
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
//超时控制时,若获取任务超时则在timedOut=true
//以下逻辑是‘阻塞队列获取任务’
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)
workQueue.take();
if (r != null)
return r;
timedOut = true; //阻塞队列特性,若特定时间获取不到,判定为超时。
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
- 关于‘阻塞队列获取任务’:
首先,在队列阻塞时候,若由于shutdown方法会中断正在阻塞的方法并抛出异常,trycatch会保证此worker不会被意外终止,而是重新进行循环判断。
其次,无论是由于线程中断抛出异常,还是没有获取到任务,都会使timedOut标志位变成false。下次进行循环时,会根据当前线程池状态等诸多因素决定是否退出。 - 退出线程池的几种情况:(默认最大线程池数量>核心线程池数量>1,不考虑极端情况)
1 worker线程数量大于设置的线程池最大容量。
2 worker线程数量大于设置的核心线程数量,且发生了超时。
3 worker线程数量小于设置的核心线程数量,但大于1,并且,线程池设置了允许核心线程关闭的变量。
4 worker线程数量等于1,任务队列为空。
以上几种情况是我认为非极端情况下发生的worker线程自动退出的情况。
processWorkerExit
在runWorker方法执行到最后被调用,处理单个worker结束后“善后工作”
入参w可以理解为是当前worker线程,completedAbruptly表示是否是非正常情况下退出。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
//completedAbruptly为true,属于worker非正常退出,线程池数量减一
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();
//下边这段代码是用来判断是否需要添加worker的。
//首先线程池状态必须为running或shutdown,且满足以下两点任意一点
//1 该worker是非正常退出
//2 虽然该worker是正常退出,但是当前线程数小于等于规定线程数min(规定线程数,根据代码中的情况进行判断)
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); //addworker方法仅仅是尝试增加
}
}
- 关于最后的添加worker逻辑:
能运行到这里说明当前线程肯定是没救了(要关闭了)。但是,在某些情况下,线程池肯定是不希望自己少一个线程的。所以它在最后加了一个重启一个新的worker线程的操作,希望弥补worker线程资源的不充足。
最后附上深入理解Java线程池其他文章链接:
https://blog.csdn.net/xiaoyuchenCSDN/article/details/83549068
https://blog.csdn.net/xiaoyuchenCSDN/article/details/83717842