不过,我倒是对线程池是如何回收工作线程比较感兴趣,所以简单分析了一下,加深对线程池的理解吧。
1 runWorker(Worker w)
工作线程启动后,就进入runWorker(Worker w)方法。里面是一个while循环,循环判断任务是否为空,若不为空,执行任务;若取不到任务,或发生异常,退出循环,执行processWorkerExit(w, completedAbruptly);在这个方法里把工作线程移除掉。
取任务的来源有两个:一个是task = w.firstTask;这个是工作线程第一次跑的时候执行的任务,最多只能执行一次;后面得从getTask()方法里取任务,看来getTask()是关键,在不考虑异常的场景下,返回null,就表示退出循环,结束线程。下一步,就得看看,什么情况下getTask()会返回null。
try {
while (task != null || (task = getTask()) != null) {
...
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
2 getTask() 返回null
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
第一种情况,线程池的状态已经是STOP,TIDYING, TERMINATED;或者是SHUTDOWN且工作队列为空;
// Check if queue empty only if necessary.(仅在必要时检查队列是否为空)
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
第二种情况,工作线程数已经大于最大线程数或当前工作线程已超时,并且,还有其他工作线程或任务队列为空。
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
3 分场景分析线程池回收工作线程
3.1 RUNNING状态下全部任务执行完成的场景
这种场景会将工作线程的数量减少到核心线程数大小(如果本来就没有超过,则不需要回收)。
假设一个线程池,核心线程数为4,最大线程数为8。一开始是4个工作线程,任务队列已经塞满,就需要将工作线程增加到8。当后面任务执行到差不多了,线程取不到任务了,就会回收到4个工作线程的状态(取决于allowCoreThreadTimeOut的值,这里讨论默认值false的情况,即核心线程不会超时。如果为true,工作线程可以全部销毁)。
可以先排除上面提到的条件1:线程池的状态已经是STOP,TIDYING, TERMINATED;或者是SHUTDOWN且工作队列为空;因为线程池一直是RUNNING,这条判断永远是false。在这个场景中,可以当条件1不存在。
下面分析取不出任务时线程是怎么运行的。
1、从任务队列取任务有两种方式:
—————————————————————————————————————————————————————————————
Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();
如果timed是true,那么走workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)也就是设置获取任务的超时时间,到时间后还没获取到任务的话则会timeOut=true。
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
timeOut=true的话,getTask有个判断会让其跳出循环,线程生命周期也自然而然的随之结束。
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
反之如果timed是false的话,那么会执行workQueue.take();不带超时时间的,则一直阻塞等待有结果返回。
其实一句话就概括了:从队列中获取任务,如果timed是true的话,则调用阻塞队列的poll方法阻塞一段时间获取任务,这段时间没任务的话,则超时设置timeOut=true,结束生命周期。否则调用take()方法一直阻塞等待任务到来,也就是核心线程为什么能一直存活的原因。
—————————————————————————————————————————————————————————————
在线程超时等待唤醒之后,发现取不出任务,timeOut变为true,进入下一次循环。
2、来到条件1的判断,线程池一直RUNNING,不进入代码块。
// Check if queue empty only if necessary.(仅在必要时检查队列是否为空)
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
3、来到条件2的判断,这时任务队列为空,条件成立,CAS减少线程数,若成功,返回null;否则,重复step1。
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
这里要注意,有可能多条线程同时通过条件2的判断,那会不会减少后线程的数量反而比预想的核心线程数少呢?比如当前线程数已经只有5条了,此时有两条线程同时唤醒,通过条件2的判断,同时减少数量,那剩下的线程数反而只有3条,和预期不一致。
实际上是不会的。为了防止这种情况,compareAndDecrementWorkerCount© 用的是CAS方法,如果CAS失败就continue,进入下一轮循环,重新判断。像上述例子,其中一条线程会CAS失败,然后重新进入循环,发现工作线程数已经只有4了,timed为false, 这条线程就不会被销毁,可以一直阻塞了(workQueue.take())。
从这里也可以看出,虽然有核心线程数,但线程并没有区分是核心还是非核心,并不是先创建的就是核心,超过核心线程数后创建的就是非核心,最终保留哪些线程,完全随机。