【高并发】通过源码深度分析线程池中Worker线程的执行流程
大家好,我是冰河~~
在《高并发之——通过ThreadPoolExecutor类的源码深度解析线程池执行任务的核心流程》一文中我们深度分析了线程池执行任务的核心流程,在ThreadPoolExecutor类的addWorker(Runnable, boolean)方法中,使用CAS安全的更新线程的数量之后,接下来就是创建新的Worker线程执行任务,所以,我们先来分析下Worker类的源码。
Worker类分析
Worker类从类的结构上来看,继承了AQS(AbstractQueuedSynchronizer类)并实现了Runnable接口。本质上,Worker类既是一个同步组件,也是一个执行任务的线程。接下来,我们看下Worker类的源码,如下所示。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
private static final long serialVersionUID = 6138294804551838833L;
//执行任务的线程类
final Thread thread;
//初始化执行的任务,第一次执行的任务
Runnable firstTask;
//完成任务的计数
volatile long completedTasks;
//Worker类的构造方法,初始化任务并调用线程工厂创建执行任务的线程
Worker(Runnable firstTask) {
setState(-1);
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
//重写Runnable接口的run()方法
public void run() {
//调用ThreadPoolExecutor类的runWorker(Worker)方法
runWorker(this);
}
//检测是否是否获取到锁
//state=0表示未获取到锁
//state=1表示已获取到锁
protected boolean isHeldExclusively() {
return getState() != 0;
}
//使用AQS设置线程状态
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()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
在Worker类的构造方法中,可以看出,首先将同步状态state设置为-1,设置为-1是为了防止runWorker方法运行之前被中断。这是因为如果其他线程调用线程池的shutdownNow()方法时,如果Worker类中的state状态的值大于0,则会中断线程,如果state状态的值为-1,则不会中断线程。
Worker类实现了Runnable接口,需要重写run方法,而Worker的run方法本质上调用的是ThreadPoolExecutor类的runWorker方法,在runWorker方法中,会首先调用unlock方法,该方法会将state置为0,所以这个时候调用shutDownNow方法就会中断当前线程,而这个时候已经进入了runWork方法,就不会在还没有执行runWorker方法的时候就中断线程。
注意:大家需要重点理解Worker类的实现。
Worker类中调用了ThreadPoolExecutor类的runWorker(Worker)方法。接下来,我们一起看下ThreadPoolExecutor类的runWorker(Worker)方法的实现。
runWorker(Worker)方法
首先,我们看下RunWorker(Worker)方法的源码,如下所示。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
//释放锁,将state设置为0,允许中断任务的执行
w.unlock();
boolean completedAbruptly = true;
try {
//如果任务不为空,或者从任务队列中获取的任务不为空,则执行while循环
while (task != null || (task = getTask()) != null) {
//如果任务不为空,则获取Worker工作线程的独占锁
w.lock();
//如果线程已经停止,或者中断线程后线程终止并且没有成功中断线程
//大家好好理解下这个逻辑
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
//中断线程
wt.interrupt();
try {
//执行任务前执行的逻辑
beforeExecute(wt, task);
Throwable thrown = null;
try {
//调用Runable接口的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;
//完成的任务数量加1
w.completedTasks++;
//释放工作线程获得的锁
w.unlock();
}
}
completedAbruptly = false;
} finally {
//执行退出Worker线程的逻辑
processWorkerExit(w, completedAbruptly);
}
}
这里,我们拆解runWorker(Worker)方法。
(1)获取当前线程的句柄和工作线程中的任务,并将工作线程中的任务设置为空,执行unlock方法释放锁,将state状态设置为0,此时可以中断工作线程,代码如下所示。
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
//释放锁,将state设置为0,允许中断任务的执行
w.unlock();
(2)在while循环中进行判断,如果任务不为空,或者从任务队列中获取的任务不为空,则执行while循环,否则,调用processWorkerExit(Worker, boolean)方法退出Worker工作线程。
while (task != null || (task = getTask()) != null)
(3)如果满足while的循环条件,首先获取工作线程内部的独占锁,并执行一系列的逻辑判断来检测是否需要中断当前线程的执行,代码如下所示。
//如果任务不为空,则获取Worker工作线程的独占锁
w.lock();
//如果线程已经停止,或者中断线程后线程终止并且没有成功中断线程
//大家好好理解下这个逻辑
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
//中断线程
wt.interrupt();
(4)调用执行任务前执行的逻辑,如下所示
//执行任务前执行的逻辑
beforeExecute(wt, task);
(5)调用Runable接口的run方法执行任务
//调用Runable接口的run方法执行任务
task.run();
(6)调用执行任务后执行的逻辑
//执行任务后执行的逻辑
afterExecute(task, thrown);
(7)将完成的任务设置为空,完成的任务数量加1并释放工作线程的锁。
//任务执行完成后,将其设置为空
task = null;
//完成的任务数量加1
w.completedTasks++;
//释放工作线程获得的锁
w.unlock();
(8)退出Worker线程的执行,如下所示
//执行退出Worker线程的逻辑
processWorkerExit(w, completedAbruptly);
从代码分析上可以看到,当从Worker线程中获取的任务为空时,会调用getTask()方法从任务队列中获取任务,接下来,我们看下getTask()方法的实现。
getTask()方法
我们先来看下getTask()方法的源代码,如下所示。
private Runnable getTask() {
//轮询是否超时的标识
boolean timedOut = false;
//自旋for循环
for (;;) {
//获取ctl
int c = ctl.get();
//获取线程池的状态
int rs = runStateOf(c);
//检测任务队列是否在线程池停止或关闭的时候为空
//也就是说任务队列是否在线程池未正常运行时为空
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
//减少Worker线程的数量
decrementWorkerCount();
return null;
}
//获取线程池中线程的数量
int wc = workerCountOf(c);
//检测当前线程池中的线程数量是否大于corePoolSize的值或者是否正在等待执行任务
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
//如果线程池中的线程数量大于corePoolSize
//获取大于corePoolSize或者是否正在等待执行任务并且轮询超时
//并且当前线程池中的线程数量大于1或者任务队列为空
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
//成功减少线程池中的工作线程数量
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//从任务队列中获取任务
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
//任务不为空直接返回任务
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
getTask()方法的逻辑比较简单,大家看源码就可以了,我这里就不重复描述了。
接下来,我们看下在正式调用Runnable的run()方法前后,执行的beforeExecute方法和afterExecute方法。
beforeExecute(Thread, Runnable)方法
beforeExecute(Thread, Runnable)方法的源代码如下所示。
protected void beforeExecute(Thread t, Runnable r) { }
可以看到,beforeExecute(Thread, Runnable)方法的方法体为空,我们可以创建ThreadPoolExecutor的子类来重写beforeExecute(Thread, Runnable)方法,使得线程池正式执行任务之前,执行我们自己定义的业务逻辑。
afterExecute(Runnable, Throwable)方法
afterExecute(Runnable, Throwable)方法的源代码如下所示。
protected void afterExecute(Runnable r, Throwable t) { }
可以看到,afterExecute(Runnable, Throwable)方法的方法体同样为空,我们可以创建ThreadPoolExecutor的子类来重写afterExecute(Runnable, Throwable)方法,使得线程池在执行任务之后执行我们自己定义的业务逻辑。
接下来,就是退出工作线程的processWorkerExit(Worker, boolean)方法。
processWorkerExit(Worker, boolean)方法
processWorkerExit(Worker, boolean)方法的逻辑主要是执行退出Worker线程,并且对一些资源进行清理,源代码如下所示。
private void processWorkerExit(Worker w, boolean completedAbruptly) {
//执行过程中出现了异常,突然中断
if (completedAbruptly)
//将工作线程的数量减1
decrementWorkerCount();
//获取全局锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//累加完成的任务数量
completedTaskCount += w.completedTasks;
//将完成的任务从workers集合中移除
workers.remove(w);
} finally {
//释放锁
mainLock.unlock();
}
//尝试终止工作线程的执行
tryTerminate();
//获取ctl
int c = ctl.get();
//判断当前线程池的状态是否小于STOP(RUNNING或者SHUTDOWN)
if (runStateLessThan(c, STOP)) {
//如果没有突然中断完成
if (!completedAbruptly) {
//如果allowCoreThreadTimeOut为true,为min赋值为0,否则赋值为corePoolSize
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
//如果min为0并且工作队列不为空
if (min == 0 && ! workQueue.isEmpty())
//min的值设置为1
min = 1;
//如果线程池中的线程数量大于min的值
if (workerCountOf(c) >= min)
//返回,不再执行程序
return;
}
//调用addWorker方法
addWorker(null, false);
}
}
接下来,我们拆解processWorkerExit(Worker, boolean)方法。
(1)执行过程中出现了异常,突然中断执行,则将工作线程数量减1,如下所示。
//执行过程中出现了异常,突然中断
if (completedAbruptly)
//将工作线程的数量减1
decrementWorkerCount();
(2)获取锁累加完成的任务数量,并将完成的任务从workers集合中移除,并释放,如下所示。
//获取全局锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//累加完成的任务数量
completedTaskCount += w.completedTasks;
//将完成的任务从workers集合中移除
workers.remove(w);
} finally {
//释放锁
mainLock.unlock();
}
(3)尝试终止工作线程的执行
//尝试终止工作线程的执行
tryTerminate();
(4)处判断当前线程池中的线程个数是否小于核心线程数,如果是,需要新增一个线程保证有足够的线程可以执行任务队列中的任务或者提交的任务。
//获取ctl
int c = ctl.get();
//判断当前线程池的状态是否小于STOP(RUNNING或者SHUTDOWN)
if (runStateLessThan(c, STOP)) {
//如果没有突然中断完成
if (!completedAbruptly) {
//如果allowCoreThreadTimeOut为true,为min赋值为0,否则赋值为corePoolSize
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
//如果min为0并且工作队列不为空
if (min == 0 && ! workQueue.isEmpty())
//min的值设置为1
min = 1;
//如果线程池中的线程数量大于min的值
if (workerCountOf(c) >= min)
//返回,不再执行程序
return;
}
//调用addWorker方法
addWorker(null, false);
}
接下来,我们看下tryTerminate()方法。
tryTerminate()方法
tryTerminate()方法的源代码如下所示。
final void tryTerminate() {
//自旋for循环
for (;;) {
//获取ctl
int c = ctl.get();
//如果线程池的状态为RUNNING
//或者状态大于TIDYING
//或者状态为SHUTDOWN并且任务队列为空
//直接返回程序,不再执行后续逻辑
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
//如果当前线程池中的线程数量不等于0
if (workerCountOf(c) != 0) {
//中断线程的执行
interruptIdleWorkers(ONLY_ONE);
return;
}
//获取线程池的全局锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//通过CAS将线程池的状态设置为TIDYING
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
//调用terminated()方法
terminated();
} finally {
//将线程池状态设置为TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
//唤醒所有因为调用线程池的awaitTermination方法而被阻塞的线程
termination.signalAll();
}
return;
}
} finally {
//释放锁
mainLock.unlock();
}
}
}
(1)获取ctl,根据情况设置线程池状态或者中断线程的执行,并返回。
//获取ctl
int c = ctl.get();
//如果线程池的状态为RUNNING
//或者状态大于TIDYING
//或者状态为SHUTDOWN并且任务队列为空
//直接返回程序,不再执行后续逻辑
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
//如果当前线程池中的线程数量不等于0
if (workerCountOf(c) != 0) {
//中断线程的执行
interruptIdleWorkers(ONLY_ONE);
return;
}
(2)获取全局锁,通过CAS设置线程池的状态,调用terminated()方法执行逻辑,最终将线程池的状态设置为TERMINATED,唤醒所有因为调用线程池的awaitTermination方法而被阻塞的线程,最终释放锁,如下所示。
//获取线程池的全局
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//通过CAS将线程池的状态设置为TIDYING
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
//调用terminated()方法
terminated();
} finally {
//将线程池状态设置为TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
//唤醒所有因为调用线程池的awaitTermination方法而被阻塞的线程
termination.signalAll();
}
return;
}
} finally {
//释放锁
mainLock.unlock();
}
接下来,看下terminated()方法。
terminated()方法
terminated()方法的源代码如下所示。
protected void terminated() { }
可以看到,terminated()方法的方法体为空,我们可以创建ThreadPoolExecutor的子类来重写terminated()方法,值得Worker线程调用tryTerminate()方法时执行我们自己定义的terminated()方法的业务逻辑。
好了,今天就到这儿吧,我是冰河,我们下期见~~
【高并发】通过源码深度分析线程池中Worker线程的执行流程的更多相关文章
- 通过源码了解ASP.NET MVC 几种Filter的执行过程 在Winform中菜单动态添加“最近使用文件”
通过源码了解ASP.NET MVC 几种Filter的执行过程 一.前言 之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神 ...
- Java 线程池中的线程复用是如何实现的?
前几天,技术群里有个群友问了一个关于线程池的问题,内容如图所示: 关于线程池相关知识可以先看下这篇:为什么阿里巴巴Java开发手册中强制要求线程池不允许使用Executors创建? 那么就来和大家探讨 ...
- 【高并发】通过源码深度解析ThreadPoolExecutor类是如何保证线程池正确运行的
大家好,我是冰河~~ 对于线程池的核心类ThreadPoolExecutor来说,有哪些重要的属性和内部类为线程池的正确运行提供重要的保障呢? ThreadPoolExecutor类中的重要属性 在T ...
- 通过源码了解ASP.NET MVC 几种Filter的执行过程
一.前言 之前也阅读过MVC的源码,并了解过各个模块的运行原理和执行过程,但都没有形成文章(所以也忘得特别快),总感觉分析源码是大神的工作,而且很多人觉得平时根本不需要知道这些,会用就行了.其实阅读源 ...
- JavaWeb——Servlet如何调用线程池中的线程?
tomcat线程池与servlet https://blog.csdn.net/qq_27817797/article/details/54025173 https://blog.csdn.net/l ...
- 详细解读Volley(五)—— 通过源码来分析业务流程
一.初始化请求队列并运行 我们用Volley时,最先开始的就是初始化请求队列,一种常见的写法如下: public class MyApplication extends Application { p ...
- Android 线程池系列教程(4) 启动线程池中的线程和中止池中线程
Running Code on a Thread Pool Thread 上一课 下一课 1.This lesson teaches you to Run a Runnable on a Thre ...
- 线程池之ThreadPoolExecutor线程池源码分析笔记
1.线程池的作用 一方面当执行大量异步任务时候线程池能够提供较好的性能,在不使用线程池的时候,每当需要执行异步任务时候是直接 new 一线程进行运行,而线程的创建和销毁是需要开销的.使用线程池时候,线 ...
- 深入浅出 Java Concurrency (33): 线程池 part 6 线程池的实现及原理 (1)[转]
线程池数据结构与线程构造方法 由于已经看到了ThreadPoolExecutor的源码,因此很容易就看到了ThreadPoolExecutor线程池的数据结构.图1描述了这种数据结构. 图1 Thre ...
随机推荐
- 用 Docker 构建 MySQL 主从环境
开源Linux 一个执着于技术的公众号 前言 本篇文章记录使用 docker-compose 以及 dockerfile 来构建基于 binlog 的 MySQL 主从环境.如果你严格按照文中的步骤进 ...
- vmware 无法安装 win 10
因为默认是 UEFI,但我们并没有 UEFI 引导分区,所以需要改成 BIOS
- EFCore常规操作生成的SQL语句一览
前言 EFCore的性能先不说,便捷性绝对是.Net Core平台下的ORM中最好用的,主要血统还百分百纯正. EFCore说到底还是对数据库进行操作,无论你是写Lamda还是Linq最后总归都是要生 ...
- Redis数据类型:五大基本数据类型及三种特殊类型
String (字符串类型) String是redis最基本的类型,你可以理解成Memcached一模一样的类型,一个key对应一个value. String类型是二进制安全的,意思是redis的st ...
- 【单片机】CH32V103串口IDLE空闲中断
CH32V103c8t6 在寻找解决接收完数据后,怎么即时判断数据已经完成了接收.发现串口有一个IDLE空闲中断.如下图描述: 意思是在串口接收完一帧数据 会产生一个中断,此时程序可判断为数据已接收完 ...
- 『忘了再学』Shell基础 — 19、使用declare命令声明变量类型
目录 1.declare命令介绍 2.声明数组变量类型 3.声明变量为环境变量 4.声明只读属性 5.补充: 1.declare命令介绍 Shell中所有变量的默认类型是字符串类型,如果你需要进行特殊 ...
- Base64 编码知识,一文打尽!
现在网站为了提升用户的浏览体验越来越多的使用了图片,而这些图片通常以 Base64 的形式存储和加载.因此各位开发工程师肯定对 Base64 毫不陌生了,那么你知道 Base64 究竟是什么,为什么要 ...
- dubbo是如何实现可扩展的?(二)
牛逼的框架,看似复杂难懂,思路其实很清晰.---me 上篇文章,在整体扩展思路上进行了源码分析,比较粗糙,现在就某些点再详细梳理下. dubbo SPi的扩展,基于一类.三注解. 一类是Extensi ...
- Windows环境下安装RabbitMQ
本地安装RabbitMQ安装注意事项: Erlang与RabbitMQ,安装路径都应不含空格符. Erlang使用了环境变量HOMEDRIVE与HOMEPATH来访问配置文件.erlang.cooki ...
- 如何使用Superset可无缝对接MRS进行自助分析
摘要:本文主要介绍如何在MRS之上使用Superset进行数据分析. 本文分享自华为云社区<使用商业智能软件Superset分析MRS数据之最佳实践>,作者: 啊喔YeYe . 1. 概要 ...