概述
前言
前面的两篇文章为分析 线程池源码 做了些铺垫, 主要内容是描述线程池的工作原理, 本文将通过分析源码的方式, 去验证线程池的工作原理。
Java 线程池 ThreadPoolExecutor 中的位运算操作
线程池 ThreadPoolExecutor 源码分析基础 - 线程池工作原理
先回顾下前面讲过的内容概要:
- 线程池的几种状态
RUNNING
、SHUTDOWN
等 - 线程池状态间的切换
- 线程池的核心参数
corePoolSize
和maximumPoolSize
等 - 线程池的工作原理
核心线程 -> 阻塞队列 -> 非核心线程 -> 拒绝策略
- 如何判断线程池的状态和线程数量:通过
AtomicInteger ctl
变量的高低位判断 - Worker 对象的作用
当掌握了以上知识点后, 再去分析具体源码就容易得多了。
正文
正文大纲
ThreadPoolExecutor
的workers
属性execute
方法addWorker
方法runWorker
方法processWorkerExit
方法
ThreadPoolExecutor#workers 属性
线程池中有一个 workers
集合,里面记录了所有的工作线程,只有拿到 mainLock
锁的线程才能访问
/**
* Set containing all worker threads in pool. Accessed only when
* holding mainLock.
*/
private final HashSet<Worker> workers = new HashSet<Worker>();
// 该属性负责记录 workers 集合的长度, 也是需要拿到锁的线程才能访问
private int largestPoolSize;
ThreadPoolExecutor#execute(Runnble command) 方法
上一篇文章 线程池 ThreadPoolExecutor 源码分析基础 - 线程池工作原理
大致分析了 execute
方法的执行流程, 这里再次提一次。
了解一些小知识点:(前面文章也详细介绍过)
ctl.get()
同时记录了线程池状态和线程个数workerCountOf
为线程池的线程数量, 取得就是ctl.get()
的后几位addWorker
暂时可以理解为开启一个线程, 下面会详细讲isRunning(c)
判断线程池是否处于运行的状态reject
表示拒绝新添加的任务
public void execute(Runnable command) {
// 参数校验
if (command == null)
throw new NullPointerException();
// 从 c 的值可以判断出线程池的状态, 以及线程池中线程的数量
int c = ctl.get();
// 1. 如果线程池的线程数量 小于 核心线程数
if (workerCountOf(c) < corePoolSize) {
// 添加一个核心线程 command 表示一个具体的任务, true 表示为核心线程
if (addWorker(command, true))
return;
c = ctl.get();
}
// 2. 如果线程池处于 RUNNING 状态(只有处于此状态,才能接受新的任务)
// 并且线程池的数量大于核心线程数, 就把任务添加到阻塞队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 3. 如果队列也满了,就创建一个非核心线程(core==false)
else if (!addWorker(command, false))
// 如果创建失败,就执行拒绝策略
reject(command);
}
总结一下:
- 如果线程池的线程数量 小于 核心线程数, 添加一个核心线程 command
- 如果线程池处于 RUNNING 状态, 并且线程池的数量大于核心线程数, 就把任务添加到阻塞队列
- 如果队列也满了,就创建一个非核心线程
- 如果非核心线程创建失败,就执行拒绝策略
ThreadPoolExecutor#addWorker(Runnable, boolean)
该方法的作用暂时可以理解为:
创建并启动一个线程
该方法有两个参数:
-
Runnable firstTask
如果该参数不为空, 那么通过addWorker
方法创建的线程会先去执行firstTask
-
boolean core
core
的值与一个if
语句有关系如果
core == true
,就把线程池的数量与corePoolSize
的值作比较,
反之, 与maximumPoolSize
的值作比较。具体看源码分析
上面的 execute
中方法已经展示了 3 种 addWorker()
的调用方式
-
addWorker(firstTask, true)
-
addWorker(firstTask, false)
-
addWorker(null, false)
下面把 addWorker
方法分为两部分, 分割方法如下所示:
第一部分
该部分是个双层死循环
在死循环过程中,获取线程池状态, 判断线程池是否可以创建新的线程,或者是否可以执行阻塞队列中的任务。
该部分最终执行结果:
要么返回 false
, 要么进入第二部分
而返回 false
有以下几种情况:
- 线程池状态 >
SHUTDOWN
, 也就是不能接受新的任务,也不能处理队列中的任务 - 线程池状态 ==
SHUTDOWN
, 表示线程池可以处理队列中的任务,但是队列却为空 - 或者线程池不能接受新的任务,但是
firstTask
却不为空
第二部分
如果顺利进入第二部分,就表示线程池可以创建新的线程
该部分的作用就是,通过 worker = new Worker()
创建 worker
对象,然后创建并开启线程,去执行 worker
对象的 run
方法。
private boolean addWorker(Runnable firstTask, boolean core) {
// 一个标记, 类似 goto, 自行了解,结合下面的 break 和 continue 就能明白什么意思
retry:
// 第一部分
for (;;) {
int c = ctl.get();
// 获取线程池状态
int rs = runStateOf(c);
// 返回 false 表示无法接收或处理任务
// 如果不想返回 false,也就是 if 语句判断不成立,需要满足以下两个条件之一:
// 1. rs < SHUTDOWN, 也就是 rs == RUNNING(我们之前讲过 RUNNING 表示可以接受新的任务)
// 2. rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()
// 2.1 rs == SHUTDOWN 表示不会接收新的任务,但是会执行阻塞队列中的任务
// 2.2 firstTask == nul 表示要执行阻塞队列中的任务
// 2.3 阻塞队列不能为空
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())
)
return false;
for (;;) {
// 获取线程池中的线程数量
int wc = workerCountOf(c);
// 如果线程数量大于最大容量(CAPACITY), 直接返回 false
// 如果 core == true ,就判断线程数量是否大于核心线程数
// 如果 core == false, 就判断线程数量是否大于最大线程数
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 通过 CAS 操作, 把线程池中的线程数量 + 1,然后跳出双层循环,进入第二部分
if (compareAndIncrementWorkerCount(c))
break retry;
// CAS 操作失败,就核对线程池的状态
c = ctl.get();
if (runStateOf(c) != rs)
// 继续从最外层循环开始执行
continue retry;
}
}
// 第二部分
// 走到这里,表示线程池可以创建新的线程,或者可以执行队列中的任务
// 两个标记,见名知义
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 下面两行代码上一篇文章介绍 Worker 对象时,详细说明过
// 包装需要执行任务(注意 firstTask 可能为 null)
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());
// 如果线程池状态为 RUNNING
// 或者
// 如果线程池状态为 SHUTDOWN 且 firstTask == null,
if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive())
throw new IllegalThreadStateException();
// 添加至 workers 集合
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
// 添加成功
workerAdded = true;
}
} finally {
// 释放锁
mainLock.unlock();
}
// 启动线程
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
// 如果线程启动失败, 就删除 workers 集合中刚添加的 Worker 对象
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
Worker#run
run 方法中调用 runWorker
方法
public void run() {
runWorker(this); this 表示具体的 worker 对象
}
ThreadPoolExecutor#runWorker
在 addWorker
方法源码的第二部分中,会先通过 new Worker(firstTask)
创建 worker
对象,此时:
- 传入的参数
firstTask != null 时
, 表示线程启动后第一个执行的任务就是firstTask
- 而传入的参数
firstTask == null
时 ,上面也讲了firstTask == null
仅仅表示要求线程池去执行阻塞队列中的任务。
然后拿到 worker 对象的 thread 去启动线程, 最后执行 runWorker
方法
简要概括:
runWorker
方法会先去执行addWorker
时传入的 firstTask
, 如果 firstTask == null
, 就去执行阻塞队列中的任务
当两者都为空时, runWorker
方法执行结束。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
// 注意这一步把 firstTask 重新赋值给了 task
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 1. task !=null, 就直接执行 task
// 2. task == null, 表示调用 addWorker 方法时传入 的 firstTask == null
// 所以就通过 getTask 方法去阻塞队列中获取任务
while (task != null || (task = getTask()) != null) {
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 {
// 执行
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#processWorkerExit
private void processWorkerExit(Worker w, boolean completedAbruptly) {
if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 统计线程池完成的任务个数
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
// 尝试设置线程池状态为 TERMINATED,
tryTerminate();
// 如果线程池的线程数量小于核心线程是, 则增加一个线程
int c = ctl.get();
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);
}
}
总结
分析 ThreadPoolExecutor
源码前, 需要了解以下内容:
-
线程池的数量和状态是由
Integer.SIZE - 3
个 2 进制位表示的, 其中高 3 位 表示线程池状态,其余低位表示线程个数 -
获取线程池数量和状态都是基于上面的二进制位来计算的
例如:
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
-
线程池的几种状态, 以及状态之间如何切换的
-
线程池
Worker
对象的作用Worker w = new Worker(firstTask); Thread t = w.thread; t.start(); 线程运行时,会去执行 Worker#run 方法 然后 run 方法会去调用 ThreadPoolExecutor#runWorker(); 方法 如果 firstTask != null , runWorker 就会去执行 firstTask 的 run 方法 如果 firstTask == null , runWorker 就会去执行阻塞队列中的任务
最后
以上就是舒适大门为你收集整理的ThreadPoolExecutor 源码分析 - execute() 方法的全部内容,希望文章能够帮你解决ThreadPoolExecutor 源码分析 - execute() 方法所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复