【并发】Java并发线程池底层原理详解与源码分析(下)
【面试题】 execute()方法与submit()方法有什么区别???
ThreadPoolExecutor 之 execute()方法详解
ThreadPoolExecutor 之 addWorker()方法详解
ThreadPoolExecutor 之 runWorker()方法详解
【并发】Java并发线程池底层原理详解与源码分析(下)
前情回顾
上篇文章地址
遗留问题解析
手动实现线程池代码
public class ThreadPoolDemo {
public static void main(String[] args) {
// 自定义线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20,
0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(10));
for (int i = 1; i <= 100; i++) {
threadPoolExecutor.execute(new MyTask(i));
}
}
}
class MyTask implements Runnable {
int i = 0;
public MyTask(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "程序员做第" + i + "个项目");
try {
Thread.sleep(3000L);//业务逻辑
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果
我们在上一个章节中提到了手动实现线程池,但是它的结果似乎有点匪夷所思!
为什么11~20会在最后面?
为什么运行会抛异常?
为什么打印日志只有前30个?
!!!先说结论!!!
在案例代码中,有100个任务,1个任务的处理时间大致需要3秒,10个核心线程,10个临时线程,阻塞队列的容量是10。
这里只会打印会前30个任务(10+10+10=30),由于在3s内核心线程和临时线程都在忙碌中,队列也满了,按照ThreadPoolExecutor默认的策略会抛出异常!
按照线程池的工作顺序,会先分配10个核心线程(1~10),再装满队列(11~20),最后分配临时线程(21~30);执行逻辑是核心线程和临时线程会先把“手头上”的任务处理完,才会去处理队列里的任务,这就是队列里的任务(11~20)最后打印的原因!!!
图解 ThreadPoolExecutor
我们可以先来研究一下,线程池的逻辑结构以及它的执行流程,以便于我们可以更加轻松的看懂源码!
下图是线程池接收任务的顺序
先装满核心线程 -> 再装满队列 -> 最后是临时线程
ThreadPoolExecutor 运行完整的流程
ThreadPoolExecutor 源码分析
接下来,我将会结合线程池ThreadPoolExecutor的源码来解析这个问题!!!
线程池源码结构
顶层接口是Executor,它只有一个方法execute();ExecutorService接口继承了Executor接口,它主要是提供了submit()。
【面试题】 execute()方法与submit()方法有什么区别???
submit
的底层还是调用了execute
(1)关于返回值的问题
submit:
有返回值,返回值(包括异常)被封装于FutureTask
对象。适用于有返回结果的任务。execute:
void
类型的函数,没有返回值,适用于没有返回的任务。
(2)关于异常处理的问题
submit:
submit
的时候并不会抛出异常(此时线程可能处于就绪状态)。只有在get
操作的时候会抛出。因为get
操作会阻塞等待线程的执行完毕。execute
:在执行的时候会直接抛出。可以通过实现UncaughtExceptionHandler
接口来完成异常的捕获。
ThreadPoolExecutor 变量详解
// 记录线程池的状态信息,高三位是状态信息,其余位表示工作的worker数量
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 表示worker数量
private static final int COUNT_BITS = Integer.SIZE - 3;
// worker容量
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;
// 后面3个都不行!!!
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
ThreadPoolExecutor 之 execute()方法详解
由于 ThreadPoolExecutor 类封装得很好,所以exectue()看起来很简单,只有短短的几行代码。
这几行代码归纳起来其实只有4个步骤!
public void execute(Runnable command) {
// 1. 任务非空校验
if (command == null)
throw new NullPointerException();
// 2. 添加核心线程执行任务
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 3. 任务入队(队列)
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);
}
// 4. 添加临时线程执行任务,如果失败则执行拒绝策略
else if (!addWorker(command, false))
reject(command);
}
看到这里,相信大家应该可以将其与上文中提到的任务提交优先级联系起来!
先装满核心线程 -> 再装满队列 -> 最后是临时线程
除去第一步,校验是否为空值的步骤外,实际上核心的步骤也就3步!
注:3个步骤中都有 addWorker(X,X) 的操作!但是它们参数都不同,所以它们代表的含义也各不相同,稍后会详解这个方法!
(1)添加核心线程执行任务
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
// 如果addWorker失败,则ctl要重新获取。
// 因为不管是状态变,还是worker数量变,ctl都已经变了,你需要重新获取最新值
c = ctl.get();
}
// 初始值 CAPACITY = 536870911
// 00011111 11111111 11111111 11111111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// c的初始值 -536870912
// 转换位二进制就是 11100000000000000000000000000000
// 11100000000000000000000000000001(+1)
// 11100000000000000000000000000010(+1)
private static int workerCountOf(int c) { return c & CAPACITY; }
int是32位了,这里用高3位表示状态,低29位表示线程数量!
workerCountOf(c)用于计算当前线程池中正在工作的线程数,如果计算的数量大于等于核心线程数,则会执行后面的逻辑!
(2)任务入队(队列)
在当前worker数量大于等于corePoolSize 或者 上面的addWorker()失败之后才会走到这里!
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);
}
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
boolean offer(E e);
查看一下当前线程的状态是否是running状态;如果是,则会执行offer()操作,尝试往工作队列里面添加这个任务,但是有可能失败(工作队列可能满了)
public boolean remove(Runnable task) {
boolean removed = workQueue.remove(task);
tryTerminate(); // In case SHUTDOWN and now empty
return removed;
}
重新获取一次ctl的值,做二次确认;如果此时状态不是running,就对之前的任务执行remove()操作,并抛异常!
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
当代码走到这里的时候,说明之前的offer()操作肯定是成功了, 这个判断是为了防止没有worker,但是队列里面有任务,没人执行!!!
addWorker(null, false),这个方法执行时只是创建了一个新的线程,但是没有传入任务,这是因为前面已经将任务添加到队列中了,这样可以防止线程池处于 running 状态,但是没有线程去处理这个任务。
(3)添加临时线程执行任务,如果失败则执行拒绝策略
else if (!addWorker(command, false))
reject(command);
执行到这里,说明核心线程此时都在工作,并且队列也满了!现在要申请让临时线程开始工作。
【问】这里为什么需要二次确认???
在多线程环境下,线程池的状态时刻在变化,而 ctl.get() 是非原子操作,很有可能刚获取了线程池状态后线程池状态就改变了。判断是否将 command 加入 队列是线程池之前的状态。倘若没有 二次确认,万一线程池处于非 running 状态(在多线程环境下很有可能发生),那么 command 永远不会执行。
ThreadPoolExecutor 之 addWorker()方法详解
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
// 每次for循环都需要获取最新的ctl值
int c = ctl.get();
// 获取这个线程的状态
int rs = runStateOf(c);
if (rs >= SHUTDOWN &&
!(rs == SHUTDOWN &&
firstTask == null &&
!workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
}
}
boolean workerStarted = false;
boolean workerAdded = false;
ThreadPoolExecutor.Worker w = null;
try {
w = new ThreadPoolExecutor.Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
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;
}
addWorker(Runnable firstTask, boolean core)
这里面有两个入参 firstTask、core
- firstTask 即是当前添加的线程需要执行的首个任务.
- core 用来标记当前执行的线程是否是核心线程
上述代码在exectue()中被调用了3次,但是每次入参都不同!
- addWorker(command, true):开启核心线程执行任务
- addWorker(null, false) :创建一个线程,从队列中获取任务
- addWorker(command, false):开启临时线程来执行任务
代码比较长,我们将代码分成两个部分来看
(1)goto部分 做“check”
retry:
for (;;) {
int c = ctl.get();
// 获取当前线程的状态
int rs = runStateOf(c);
// 不需要临时线程
// 相当于rs >= shutdown && (rs != shutdown || firstTask != null || workQueue.isEmpty())
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
// 判断容量
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 尝试通过CAS方式,如果前面没有问题,这里自增+1
if (compareAndIncrementWorkerCount(c))
// 自增后退出循环,执行后面逻辑!
break retry;
c = ctl.get();
// 如果线程池状态发生变化,重新从最外层循环
if (runStateOf(c) != rs)
continue retry;
}
}
判断是否要开临时线程
if (rs >= SHUTDOWN &&
!(rs == SHUTDOWN &&
firstTask == null &&
!workQueue.isEmpty()))
return false;
这个代码有一点绕!翻译过来其实就是:
rs >= shutdown && (rs != shutdown || firstTask != null || workQueue.isEmpty())
- rs > shutdown:线程池状态处于STOP,TIDYING,TERMINATED时,添加工作线程失败,不接受新任务
- rs >= shutdown && firstTask != null:线程池状态处于 SHUTDOWN,STOP,TIDYING,TERMINATED状态且worker的首个任务不为空时,添加工作线程失败,不接受新任务。
- rs >= shutdown && workQueue.isEmppty:线程池状态处于 SHUTDOWN,STOP,TIDYING,TERMINATED状态且阻塞队列为空时,添加工作线程失败,不接受新任务。
当满足这3种情况的时候,就不要 “加 Worker” 了!!!
判断容量
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
- 工作线程数量是否超过可表示的最大容量(CAPACITY)
- 如果添加核心线程,是否超过最大核心线程容量(corePoolSize)
- 如果添加临时线程,是否超过线程池最大线程容量(maximumPoolSize)
如果容量符合“标准”,就会执行后面的CAS操作;如果CAS执行成功,退出循环,进入第二阶段!
private boolean compareAndIncrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect + 1);
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
(2)“干事情”
// 注意 Worker代表线程,Task表示任务
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//创建Worker对象实例
w = new Worker(firstTask);
//获取Worker对象里的线程
final Thread t = w.thread;
if (t != null) {
//开启可重入锁,独占
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//获取线程池运行状态
int rs = runStateOf(ctl.get());
//满足 rs < SHUTDOWN 判断线程池是否是RUNNING,或者
//rs == SHUTDOWN && firstTask == null 线程池如果是SHUTDOWN,
//且首个任务firstTask为空,
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive())
throw new IllegalThreadStateException();
//将Worker实例加入线程池workers
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
//线程添加成功标志位 -> true
workerAdded = true;
}
} finally {
//释放锁
mainLock.unlock();
}
//如果worker实例加入线程池成功,则启动线程,同时修改线程启动成功标志位 -> true
if (workerAdded) {
// 执行 start 会调 run方法!!!
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
//添加线程失败 回滚
addWorkerFailed(w);
}
return workerStarted;
最核心的部分如下所示
try {
int rs = runStateOf(ctl.get());
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();
}
- 首先检查线程池的状态,当线程处于 RUNNING 状态 或者 线程处于 SHUTDOWN 状态且当前线程的 firstTask 为空,满足以上条件时才能将 worker 实例添加进线程池,即workers.add(w);
- 同时修改 largestPoolSize,largestPoolSize变量用于记录目前最大线程数。
- 将标志位 workerAdded 设置为 true,表示添加线程成功。
- 无论成功与否,在 finally 中都必须执行 mainLock.unlock()来释放锁。
if (workerAdded) {
t.start();
workerStarted = true;
}
当上面的条件都满足的时候,会执行 start() 方法,会调 run方法!!!
ThreadPoolExecutor 之 runWorker()方法详解
在线程池中,其实还有两个概念——提交优先级 和 执行优先级
上面的源码分析,其中 exectue() 和 addWorker() 是提交部分;而这里的 runWorker() 是执行部分
在上面的addWorker()方法中调了start()方法,JVM会通过 start()来调用run()方法!!!自然最后会调到runWorker()
if (workerAdded) {
// 执行 start 会调 run方法!!!
t.start();
workerStarted = true;
}
public void run() {
runWorker(this);
}
final void runWorker(ThreadPoolExecutor.Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock();
boolean completedAbruptly = true;
try {
// 执行优先级!!!
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);
}
}
执行优先级源码实现
while (task != null || (task = getTask()) != null) {...}
为什么说这里可以将执行优先级体现得“淋漓尽致”呢???
这个task表示任务,当 task 为null的时候,才会执行 getTask() 即从队列中获取任务,并交给线程处理!
这里又一次的说明了 addWorker(null,false) 的作用!!!
所以,总的来说,这里的逻辑是核心线程和临时线程先处理完自己手头上的任务后,才会去线程池里拿!!!
beforeExecute(wt, task);
afterExecute(task, thrown);
上面的代码中可以看到有beforeExecute、afterExecute,它们都是钩子函数,可以分别在子类中重写它们用来扩展 ThreadPoolExecutor,例如添加日志、计时、监视或者统计信息收集的功能。
线程复用机制源码实现
processWorkerExit(w, completedAbruptly);
private void processWorkerExit(ThreadPoolExecutor.Worker w, boolean completedAbruptly) {
if (completedAbruptly)
decrementWorkerCount();
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
completedTaskCount += w.completedTasks;
workers.remove(w);
} finally {
mainLock.unlock();
}
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;
}
// 线程复用!!!
addWorker(null, false);
}
}
在 runWorker() 代码的最后的finally里面,会调 processWorkerExit() 方法,这个方法非常的重要!因为线程池的复用机制就是在这里体现的!!!
最后这里调了 addWorker(null, false) 表示会创建一个新的线程(但是没有任务),其实就是表示当前线程将自己之前手头上的活处理完了,现在又可以接活了!!!