了解下JUC的线程池学习七(getTask方法源码分析)
1.介绍
getTask()
方法是工作线程在while
死循环中获取任务队列中的任务对象的方法
2.源码
private Runnable getTask() {
// 记录上一次从队列中拉取的时候是否超时
boolean timedOut = false; // Did the last poll() time out?
// 注意这是死循环
for (;;) {
int c = ctl.get();
// Check if queue empty only if necessary.
// 第一个if:如果线程池状态至少为SHUTDOWN,也就是rs >= SHUTDOWN(0),则需要判断两种情况(或逻辑):
// 1. 线程池状态至少为STOP(1),也就是线程池正在停止,一般是调用了shutdownNow()方法
// 2. 任务队列为空
// 如果在线程池至少为SHUTDOWN状态并且满足上面两个条件之一,则工作线程数wc减去1,然后直接返回null
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
// 跑到这里说明线程池还处于RUNNING状态,重新获取一次工作线程数
int wc = workerCountOf(c);
// Are workers subject to culling?
// timed临时变量勇于线程超时控制,决定是否需要通过poll()此带超时的非阻塞方法进行任务队列的任务拉取
// 1.allowCoreThreadTimeOut默认值为false,如果设置为true,则允许核心线程也能通过poll()方法从任务队列中拉取任务
// 2.工作线程数大于核心线程数的时候,说明线程池中创建了额外的非核心线程,这些非核心线程一定是通过poll()方法从任务队列中拉取任务
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
// 第二个if:
// 1.wc > maximumPoolSize说明当前的工作线程总数大于maximumPoolSize,说明了通过setMaximumPoolSize()方法减少了线程池容量
// 或者 2.timed && timedOut说明了线程命中了超时控制并且上一轮循环通过poll()方法从任务队列中拉取任务为null
// 并且 3. 工作线程总数大于1或者任务队列为空,则通过CAS把线程数减去1,同时返回null,
// CAS把线程数减去1失败会进入下一轮循环做重试
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 如果timed为true,通过poll()方法做超时拉取,keepAliveTime时间内没有等待到有效的任务,则返回null
// 如果timed为false,通过take()做阻塞拉取,会阻塞到有下一个有效的任务时候再返回(一般不会是null)
Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :workQueue.take();
// 这里很重要,只有非null时候才返回,null的情况下会进入下一轮循环
if (r != null)
return r;
// 跑到这里说明上一次从任务队列中获取到的任务为null,一般是workQueue.poll()方法超时返回null
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
3.分析
方法中,有两处十分庞大的if
逻辑,对于第一处if
可能导致工作线程数减去1直接返回null
的场景有:
- 线程池状态为
SHUTDOWN
,一般是调用了shutdown()
方法,并且任务队列为空。 - 线程池状态为
STOP
。
对于第二处if
,逻辑有点复杂,先拆解一下:
// 工作线程总数大于maximumPoolSize,说明了通过setMaximumPoolSize()方法减少了线程池容量
boolean b1 = wc > maximumPoolSize;
// 允许线程超时同时上一轮通过poll()方法从任务队列中拉取任务为null
boolean b2 = timed && timedOut;
// 工作线程总数大于1
boolean b3 = wc > 1;
// 任务队列为空
boolean b4 = workQueue.isEmpty();
boolean r = (b1 || b2) && (b3 || b4);
if (r) {
if (compareAndDecrementWorkerCount(c)){
return null;
}else{
continue;
}
}
说明:这段逻辑大多数情况下是针对非核心线程。
在execute()
方法中,当线程池总数已经超过了corePoolSize
并且还小于maximumPoolSize
时,
当任务队列已经满了的时候,会通过addWorker(task,false)
添加非核心线程。
而这里的逻辑恰好类似于addWorker(task,false)
的反向操作,用于减少非核心线程,
使得工作线程总数趋向于corePoolSize
。如果对于非核心线程,上一轮循环获取任务对象为null
,
这一轮循环很容易满足timed && timedOut
为true,
这个时候getTask()
返回null会导致Worker#runWorker()
方法跳出死循环,
之后执行processWorkerExit()
方法处理后续工作,而该非核心线程对应的Worker
则变成“游离对象”,
等待被JVM回收。当allowCoreThreadTimeOut
设置为true的时候,这里分析的非核心线程的生命周期终结逻辑同时会适用于核心线程。
4.keepAliveTime的意义
当允许核心线程超时,也就是allowCoreThreadTimeOut
设置为true的时候,此时keepAliveTime
表示空闲的工作线程的存活周期。
默认情况下不允许核心线程超时,此时keepAliveTime
表示空闲的非核心线程的存活周期。
学习来源:https://www.cnblogs.com/throwable/p/13574306.html