一、runWorker(Worker w)

任务在被提交到线程池之后,就会进入runWorker(Worker w)方法,这里面通过getTask()来获取任务,如果取不到任务,就会退出循环执行processWorkerExit(w,completedAbruptly),把这个工作线程移除掉。
取出任务主要在于getTask()方法,线程如果要回收就要看getTask()在什么时候会返回null

二、getTask()返回null的情况

第一种情况,线程池的状态已经是STOP,TIDYING, TERMINATED,或者是SHUTDOWN且工作队列为空;
第二种情况,工作线程数已经大于最大线程数或当前工作线程已超时,且,还有其他工作线程或任务队列为空。这点比较难理解,总之先记住,后面会用。


java new 线程池 占用栈内存 java 线程池释放_线程池

三、分析线程池回收工作线程

3.1 未调用shutdown() ,RUNNING状态下全部任务执行完成的场景

比如一个线程池,核心线程数为4,最大线程数为8。一开始是4个工作线程,当任务把任务队列塞满,就得将工作线程增加到8. 当后面任务执行到差不多了,线程取不到任务了,就会回收到4个工作线程的状态(取决于allowCoreThreadTimeOut的值,这里讨论默认值false的情况,即核心线程不会超时。如果为true,工作线程可以全部销毁)。allowCoreThreadTimeOut根据我的理解是是否允许核心线程超时,通常为false,即回收的时候是不会回收核心线程的,会一直在workQueue.take()方法中阻塞等待,获取任务。

step1. 从任务队列取任务有两种方式,超时等待还是可以一直阻塞下去。决定因素是timed变量。该变量在前面赋值,如果当前线程数大于核心线程数,变量timed为true, 否则为false(上面说了,这里只讨论allowCoreThreadTimeOut为false的情况)。很明显,现在讨论的是timed为true的情况。keepAliveTime一般不设置,默认值为0,所以基本上可以认为是不阻塞,马上返回取任务的结果。在线程超时等待唤醒之后,发现取不出任务,timeOut变为true,进入下一次循环。

step2. 来到条件1的判断,线程池一直RUNNING, 不进入代码块。

step3. 来到条件2的判断,这时任务队列为空,条件成立,CAS减少线程数,若成功,返回null,否则,重复step1。

这里要注意,有可能多条线程同时通过条件2的判断,那会不会减少后线程的数量反而比预想的核心线程数少呢?

比如当前线程数已经只有5条了,此时有两条线程同时唤醒,通过条件2的判断,同时减少数量,那剩下的线程数反而只有3条,和预期不一致。

实际上是不会的。为了防止这种情况,compareAndDecrementWorkerCount© 用的是CAS方法,如果CAS失败就continue,进入下一轮循环,重新判断。

像上述例子,其中一条线程会CAS失败,然后重新进入循环,发现工作线程数已经只有4了,timed为false, 这条线程就不会被销毁,可以一直阻塞了(workQueue.take())。

timed

/**
*【重点】判断线程是否超时,这里会针对两种情况判断
*  1.设置allowCoreThreadTimeOut参数默认false
*    如果为true表示核心线程数也会被回收
*  2.判断当前线程池的数量是否大于核心线程数
*/
int wc = workerCountOf(c);
boolean timed = allowCoreThreadOut || wc>corePoolSize;

这里就比较清楚了, 如果 timed 为 True, 线程经过 非核心线程过期时间后还没有获取到任务, 则方法结束, 后续会将 Worker 进行回收

如果没有设置 allowCoreThreadTimeOut 为 True, 以及当前线程池内线程数量不大于核心线程

那么从阻塞队列获取的话是 take(), take() 会 一直阻塞, 等待任务的添加返回

这样也就间接达到了核心线程数不会被回收的效果


java new 线程池 占用栈内存 java 线程池释放_java new 线程池 占用栈内存_02