ThreadPoolExecutor中execute()方法原理
序言
线程池的相关参数,创建,执行,以及运行原理。
涉及问题
需求:涉及大数据批量数据对比处理
方案 :定时任务,中根据数据来源创建线程池,加入队列,批量处理大数据量
涉及思考问题:ThreadPoolExecutor
中execute()
方法原理
execute()执行原理
- 如果当前运行的线程,少于
corePoolSize
,则创建一个新的线程来执行任务。 - 如果运行的线程等于或多于
corePoolSize
,将任务加入BlockingQueue
。 - 如果
BlockingQueue
内的任务超过上限,则创建新的线程来处理任务。 - 如果创建的线程数是单钱运行的线程超出
maximumPoolSize
,任务将被拒绝策略拒绝。
那么问题来了,如果当前没有线程数即为0,阻塞队列也是无界的,按照我们上面所说的四条,那么没有线程被执行,但是会无限向队列中添加,
测试代码
public class threadTest {
private final static ThreadPoolExecutor executor = new ThreadPoolExecutor(0,1,0, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger();
while (true) {
executor.execute(() -> {
System.out.println(atomicInteger.getAndAdd(1));
});
}
}
}
结果里面的System.out.println(atomicInteger.getAndAdd(1));
语句执行了,与上面的描述矛盾了。到底发生了什么?线程池创建线程的逻辑是什么?我们还是从源码来看看到底线程池的逻辑是什么?
线程池
ctl
要了解线程池,首先要了解线程池里面的状态控制变量ctl
- 线程池的ctl是一个原子的
AtomicInteger
- 这个
ctl
包含两个参数:runState
线程的状态workerCount
激活的线程数
- 它的低29位用于存放当前的线程数, 因此一个线程池在理论上最大的线程数是 536870911; 高 3 位是用于表示当前线程池的状态, 其中高三位的值和状态对应如下:
- 111: RUNNING
- 000: SHUTDOWN
- 001: STOP
- 010: TIDYING
- 011: TERMINATED
为了能够使用ctl
,线程池提供了三个方法:
// Packing and unpacking ctl
//获取线程池的状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
//获取线程池中工作线程数量
private static int workerCountOf(int c) { return c & CAPACITY; }
//根据线程池的状态和工作线程数得到ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }
execute
外界通过 execute
这个方法来向线程池提交任务。
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//如果工作线程数小于核心线程数,
if (workerCountOf(c) < corePoolSize) {
//执行addWorker,会创建一个核心线程,如果创建失败,重新获取ctl
if (addWorker(command, true))
return;
c = ctl.get();
}
//如果工作线程数大于等于核心线程数,线程池的状态是RUNNING,并且可以添加进队列
//(RUNNING状态下)如果添加失败,说明是队列已经满了,接着就去创建新的线程,如果大于最大线程数,则执行拒绝策略
//如果线程池不是RUNNING状态,则执行拒绝策略(当然还会调addWorker进行判断一次)
if (isRunning(c) && workQueue.offer(command)) {
//再次获取ctl,进行双重检索(也就是对线程池的状态再次检查一遍)
int recheck = ctl.get();
//如果线程池是不是处于RUNNING的状态,那么就会将任务从队列中移除,
//如果移除失败,则会判断工作线程是否为0 ,如果过为0 就创建一个非核心线程
//如果移除成功,就执行拒绝策略,因为线程池已经不可用了;
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
//线程池挂了或者大于最大线程数
reject(command);
}
通过上面的代码我们可以得出几个结论:
①** 当工作线程数小于核心线程数时,会创建核心线程数;**
②如果工作线程数大于等于核心线程数时,会尝试将任务添加进队列;
-
如果成功,会对线程池的状态进行二次验证(因为可能存在刚好线程池的状态发生了改变的情况),只要是
RUNNING
的状态,就一定要保证有工作线程还在。
③ 二次验证时,如果线程池不是处于RUNNING
的状态,那么就会将任务从队列中移除; -
如果移除失败,则会判断工作线程是否为0 ,如果过为0 就创建一个非核心线程;
-
如果移除成功,就执行拒绝策略,因为线程池已经不可用了;
④ 二次验证时,如果线程池处于RUNNING的状态,会判断工作线程是否为0 ,如果过为0 就创建一个非核心线程; -
如果失败,说明是队列已经满了,接着就去创建新的线程,如果大于最大线程数,则执行拒绝策略
疑问:
代码中addWorker
方法,总共出现3次,第一次是创建核心线程,其他2次都是非核心线程,为什么呢?
第一次毋庸置疑;关键是第二次,我一开始很迷惑,第二次是在工作线程为0时调用的,为什么不传
true
是判断核心线程;我们现在假设此时是创建核心线程,即false
改为true
;那么addWorker
方法中wc >= (core ? corePoolSize : maximumPoolSize)
这个地方会去判断当前工作线程是否大于核心线程,在高并发的情况下,会存在其他线程将工作线程的数量创建的大于核心线程数,导致返回false
,并且不会创建新线程,虽然有工作线程的存在,但是会导致原本可以及时处理的任务,要去排队执行。
第三次显而易见必须是false
,因为经过前面几次后,剩下的结果,要么是线程池挂了,要是是队列满了,所以一定是创建非核心线程;
addWorker
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
//判断线程池的是否可以接收新任务
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增加工作线程数
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
//如果添加失败,并且线程池状态发生了改变,重来一遍
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
//上面的逻辑是考虑是否能够添加线程,如果可以使用cas来增加工作线程数量
//下面正式启动线程
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//新建worker
w = new Worker(firstTask);
// 获取当前线程
final Thread t = w.thread;
if (t != null) {
//获取重入锁
final ReentrantLock mainLock = this.mainLock;
//锁住
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
// rs < SHUTDOWN -- 状态即为:RUNNING
//rs == SHUTDOWN && firstTask == null
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
//如果线程已经启动,抛出异常
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
//workers是一个HashSet,必须在锁住的情况下,操作
workers.add(w);
int s = workers.size();
//设置largestPoolSize ,标记workerAdded
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
//如果添加成功,启动线程
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
//启动线程失败,回滚
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
该方法的思路:
- 考虑是否能够添加线程,如果可以使用
CAS
来增加工作线程数量 - 正式启动线程
Worker
本身也是一个任务因为其实现了Runnable
,并且其继承了AbstractQueuedSynchronizer
,所以也具备锁的效果。而我们提交的任务,是在Worker
方法中的run
方法中调用的。
为什么启动Worker
属性中的Thread
,就能运行Worker
其构造函数是这样的:
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
可以看出这句this.thread = getThreadFactory().newThread(this);
就把当前的Worker
对象作为任务传给了新建的进程。这样启动进程时,它也就启动了。
源码对比
jdk8
在jdk8中addWorker()方法是:
...省略...
for (;;) {
//获取工作线程的数量
int wc = workerCountOf(c);
//判断工作线程的数量是否大于等于线程池的上限或者核心或者最大线程数
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
...省略...
jdk11
但是到了jdk11中,其优化了下,变成了如下:
for (;;) {
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
本质上含义是一样的,都是
判断工作线程的数量是否大于等于线程池的上限或者核心或者最大线程数
为什么可以改写成这样(core ? corePoolSize : maximumPoolSize) & COUNT_MASK)呢?
COUNT_MASK就是jdk8中的CAPACITY。
这是因为COUNT_MASK
表示的是000 11111 11111111 11111111 1111111表示是线程池最大能表示的数字,即线程池的上限;可以看到其高位三个数都是0,因为高位表示的是runstate
,即线程池运行状态;都为0,也就是不需要这三个数;那么这个COUNT_MASK和任何数字做&运算;都会等于该数字本身,并且做&运算,保证了这个数字不会超过COUNT_MASK
本身,即线程池的上限;
总结
- 当工作线程小于核心线程时,会创建核心线程。
- 如果线程池中的线程数量大于等于
corePoolSize
,但队列workQueue
未满,则将新添加的任务放到workQueue
中,按照FIFO
的原则依次等待执行(线程池中有线程空闲出来后依次将队列中的任务交付给空闲的线程执行); - 当线程池处于非
RUNNING
状态时,会尝试将刚刚加入到队列的任务移除掉,如果移除失败,并且工作线程为0情况下,就会尝试创建一个非核心线程,来消费队列。 - 如果线程池中的线程数量大于等于
corePoolSize
,且队列workQueue
已满,但线程池中的线程数量小于maximumPoolSize
,则会创建新的线程来处理被添加的任务; - 如果线程池中的线程数量等于了
maximumPoolSize
,就用RejectedExecutionHandler
来做拒绝处理
当有新的任务要处理时,先看线程池中的线程数量是否大于
corePoolSiz
e,再看缓冲队列workQueue
是否满,最后看线程池中的线程数量是否大于maximumPoolSize
另外,当线程池中的线程数量大于corePoolSize
时,如果里面有线程的空闲时间超过了keepAliveTime
,就将其移除线程池
原文地址:[https://blog.csdn.net/u013066244/article/details/87898187]