了解下JUC的线程池学习六(工作线程内部类Worker源码分析)
1.介绍
线程池中的每一个具体的工作线程被包装为内部类Worker
实例,
Worker
继承于AbstractQueuedSynchronizer(AQS),实现了
Runnable
接口
2.实现了Runnable
接口
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
// 保存ThreadFactory创建的线程实例,如果ThreadFactory创建线程失败则为null
final Thread thread;
// 保存传入的Runnable任务实例
Runnable firstTask;
// 记录每个线程完成的任务总数
volatile long completedTasks;
// 唯一的构造函数,传入任务实例firstTask,注意可以为null
Worker(Runnable firstTask) {
// 禁止线程中断,直到runWorker()方法执行
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
// 通过ThreadFactory创建线程实例,注意一下Worker实例自身作为Runnable用于创建新的线程实例
this.thread = getThreadFactory().newThread(this);
}
// 委托到外部的runWorker()方法,注意runWorker()方法是线程池的方法,而不是Worker的方法
public void run() {
runWorker(this);
}
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
// 是否持有独占锁,state值为1的时候表示持有锁,state值为0的时候表示已经释放锁
protected boolean isHeldExclusively() {
return getState() != 0;
}
// 独占模式下尝试获取资源,这里没有判断传入的变量,直接CAS判断0更新为1是否成功,成功则设置独占线程为当前线程
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 独占模式下尝试是否资源,这里没有判断传入的变量,直接把state设置为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();
}
// 启动后进行线程中断,注意这里会判断线程实例的中断标志位是否为false,只有中断标志位为false才会中断
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
第一点:Worker
的构造函数里面的逻辑十分重要,通过ThreadFactory
创建的Thread
实例同时传入Worker
实例,
因为Worker
本身实现了Runnable
,所以可以作为任务提交到线程中执行。
只要Worker
持有的线程实例w
调用Thread#start()
方法就能在合适时机执行Worker#run()
。简化一下逻辑如下:
// addWorker()方法中构造
Worker worker = createWorker();
// 通过线程池构造时候传入
ThreadFactory threadFactory = getThreadFactory();
// Worker构造函数中
Thread thread = threadFactory.newThread(worker);
// addWorker()方法中启动
thread.start();
第二点:Worker
继承自AQS
,这里使用了AQS
的独占模式,这里有个技巧是构造Worker
的时候,
把AQS
的资源(状态)通过setState(-1)
设置为-1,
这是因为Worker
实例刚创建时AQS
中state
的默认值为0,此时线程尚未启动,
不能在这个时候进行线程中断,见Worker#interruptIfStarted()
方法。
Worker
中两个覆盖AQS
的方法tryAcquire()
和tryRelease()
都没有判断外部传入的变量,前者直接CAS(0,1)
,
后者直接setState(0)
。
3.核心方法ThreadPoolExecutor#runWorker()
:
final void runWorker(Worker w) {
// 获取当前线程,实际上和Worker持有的线程实例是相同的
Thread wt = Thread.currentThread();
// 获取Worker中持有的初始化时传入的任务对象,这里注意存放在临时变量task中
Runnable task = w.firstTask;
// 设置Worker中持有的初始化时传入的任务对象为null
w.firstTask = null;
// 由于Worker初始化时AQS中state设置为-1,这里要先做一次解锁把state更新为0,允许线程中断
w.unlock(); // allow interrupts
// 记录线程是否因为用户异常终结,默认是true
boolean completedAbruptly = true;
try {
// 初始化任务对象不为null,或者从任务队列获取任务不为空(从任务队列获取到的任务会更新到临时变量task中)
// getTask()由于使用了阻塞队列,这个while循环如果命中后半段会处于阻塞或者超时阻塞状态,
//getTask()返回为null会导致线程跳出死循环使线程终结
while (task != null || (task = getTask()) != null) {
// Worker加锁,本质是AQS获取资源并且尝试CAS更新state由0更变为1
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
// 如果线程池正在停止(也就是由RUNNING或者SHUTDOWN状态向STOP状态变更),
//那么要确保当前工作线程是中断状态
// 否则,要保证当前线程不是中断状态
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
// 钩子方法,任务执行前
beforeExecute(wt, task);
try {
task.run();
// 钩子方法,任务执行后 - 正常情况
afterExecute(task, null);
} catch (Throwable ex) {
// 钩子方法,任务执行后 - 异常情况
afterExecute(task, ex);
throw ex;
}
} finally {
// 清空task临时变量,这个很重要,否则while会死循环执行同一个task
task = null;
// 累加Worker完成的任务数
w.completedTasks++;
// Worker解锁,本质是AQS释放资源,设置state为0
w.unlock();
}
}
// 走到这里说明某一次getTask()返回为null,线程正常退出
completedAbruptly = false;
} finally {
// 处理线程退出,completedAbruptly为true说明由于用户异常导致线程非正常退出
processWorkerExit(w, completedAbruptly);
}
}
这里重点拆解分析一下判断当前工作线程中断状态的代码:
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
// 先简化一下判断逻辑,如下
// 判断线程池状态是否至少为STOP,rs >= STOP(1)
boolean atLeastStop = runStateAtLeast(ctl.get(), STOP);
// 判断线程池状态是否至少为STOP,同时判断当前线程的中断状态并且清空当前线程的中断状态
boolean interruptedAndAtLeastStop = Thread.interrupted() && runStateAtLeast(ctl.get(), STOP);
if (atLeastStop || interruptedAndAtLeastStop && !wt.isInterrupted()){
wt.interrupt();
}
注:Thread.interrupted()
方法获取线程的中断状态同时会清空该中断状态,
这里之所以会调用这个方法是因为在执行上面这个if
逻辑同时外部有可能调用shutdownNow()
方法,
shutdownNow()
方法中也存在中断所有Worker
线程的逻辑,
但是由于shutdownNow()
方法中会遍历所有Worker
做线程中断,
有可能无法及时在任务提交到Worker
执行之前进行中断,
所以这个中断逻辑会在Worker
内部执行,就是if
代码块的逻辑。
这里还要注意的是:STOP
状态下会拒绝所有新提交的任务,不会再执行任务队列中的任务,
同时会中断所有Worker
线程。也就是,即使任务Runnable已经runWorker()
中前半段逻辑取出,
只要还没走到调用其Runnable#run(),都有可能被中断。
假设刚好发生了进入if
代码块的逻辑同时外部调用了shutdownNow()
方法,
那么if
逻辑内会判断线程中断状态并且重置,那么shutdownNow()
方法中调用的interruptWorkers()
就不会因为中断状态判断出现问题导致二次中断线程(会导致异常)。
4.runWorker()
方法的核心流程
第一步:Worker
先执行一次解锁操作,用于解除不可中断状态。
第二步:通过while
循环调用getTask()
方法从任务队列中获取任务(当然,首轮循环也有可能是外部传入的firstTask任务实例)。
第三步:如果线程池更变为STOP
状态,则需要确保工作线程是中断状态并且进行中断处理,否则要保证工作线程必须不是中断状态。
第四步:执行任务实例Runnale#run()
方法,任务实例执行之前和之后(包括正常执行完毕和异常执行情况)
分别会调用钩子方法beforeExecute()
和afterExecute()
。
第五步:while
循环跳出意味着runWorker()
方法结束和工作线程生命周期结束(Worker#run()
生命周期完结),
会调用processWorkerExit()
处理工作线程退出的后续工作。
5.图解
学习来源:https://www.cnblogs.com/throwable/p/13574306.html