这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战
worker
在之前的描述中我们已经清楚:
- worker是线程池中,对于第一个线程和状态的封装。
我们看一下worker的签名:
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
注意:worker继承并实现了Runnable接口;
我们是通过以下的构造方法来构建worker的:
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
那么说明:
- 当我们使用 worker(null) 进行构建的时候,我们的任务信息是没有进入到worker中的。
注意,此时从getThreadFactory().newThread(this) 方法中获取并被封装到worker中的线程,线程中封装的是worker任务,上面描述的:
t.start()
实际上启动的就是worker的run方法了。
接下来看一下worker的run方法有什么不一样的地方:
public void run() {
runWorker(this);
}
final void runWorker(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);
}
}
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
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;
}
}
}
核心是:
-
一旦启动了worker,就会以当前线程,来启动任务:
Thread wt = Thread.currentThread();
随后判断是否有指定的firstTask任务,如果没有,就执行 getTask() 方法来获取任务,不管任务获取到与否都会循环这个过程:
while (task != null || (task = getTask()) != null)
执行完获取到的线程后,会回来继续获取线程来进行工作
getTask里:
-
先判断:
- 当前线程池是不是挂了,或者工作队列空了?
- 上次获取是不是超时了?核心线程超时了?
-
前面的条件如果通过了,就根据指定的 allowCoreThreadTimeOut,来进行限时获取工作,或者不限时地获取
循环这个过程,直到判断不通过,或者获取到线程为止。
到这里我们就明白为什么说线程复用了:
-
线程池维护了一个workQueue,一旦我们提交了任务,那么:
-
如果是核心线程:我们提交的这个任务会被封装成worker对象,在线程池中永无止境地工作:
- 提交的任务结束后,会继续持续地去获取workQueue中的任务来运行。
-
我们也可以解释为什么有的地方会执行:
addWorker(null, false);
那么线程池空闲线程的回收机制是如何实现的?
现在我们知道:
- 当worker执行完firstTask之后,会通过getTask()方法,来获取任务去执行。
注意getTask()里的这段代码:
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
如果设置了超时相关配置,并且工作队列已经空了,此时就会返回一个null。
回到worker的runWorker方法中,当没有任务可以获取到时,最后就会执行这个方法:
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();
}
//注意,这里是根据ctl的值进行判断的,因此并不意味着worker退出的时候就一定会去尝试关闭线程池
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);
}
}
这里就能看出来线程的回收,实际上就是在线程池未彻底关闭的情况下,将worker移出工作队列,随后添加一个新的worker来执行任务。
通过这两篇文章,解释了我们常见对于线程池的描述:
-
线程池三个大小的描述:(execute方法中都描述了)
-
核心线程(corePoolSize)未满的时候,是直接添加线程进行工作的(addWorker(task,true) )
-
线程池在核心线程满而工作队列(workQueue)未满的时候,会往工作队列中添加任务,而不是添加worker
workQueue.offer(command)
-
线程池工作队列满了就会继续添加worker,失败则调用拒绝策略
else if (!addWorker(command, false)){reject(command);}
-
此处和线程池最大大小(maximumPoolSize)的相关性在addWorker方法中
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false;
-
-
-
线程池工作线程的描述:
-
线程池的线程复用:
-
实际上线程池中,工作线程被封装为worker,在工作执行完之后会去工作队列中获取新的任务来执行(getTask)
-
等待超时的线程会被回收。
-
在worker的runWorker方法中,当获取不到任务的时候就会调用processWorkerExit方法并退出runWorker方法的执行,在processWorkerExit方法中,会将该worker从维护worker的workers中剔除。
-
此时如果工作的线程数量超过了核心线程数[1] ,那么就返回了,相当于注销了这个工作线程;如果没有,会新增一个没有firstTask的工作线程,随后使用addWorker(null,false) 。
-
如果通过allowCoreThreadTimeOut设置了不允许核心线程超时,那么上面的核心线程数[1] 的地方就是0了。
-
此时需要注意:我们新添加的worker对象,是非核心的,并且此处没有添加锁等机制。
-
非核心线程中,对应的判断退出与否是:
int wc = workerCountOf(c);
if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false;
也就意味着:此处可能会出现添加了worker之后,出现了工作线程数大于corePoolSize的情况,但这并没有什么太大的问题,因为此时工作队列是空的,如果获取不到任务自然会进行这部分worker对象的回收。
-
-
-
-
-
-
线程复用是错误的:
-
通过上面的描述我们可以看到,实际上复用的是worker对应的线程,而worker本身也是一种现成的继承类,因此本身这种说法就模棱两可:
你既可以说复用的是worker对象,也可以说复用的是(worker所使用的)线程,只是看描述的维度如何罢了。
-
\