决战圣地玛丽乔亚Day35---线程池的回收

线程池的核心线程如何回收?

首先要了解线程池的状态:
Runing: 运行状态,能接受新任务并处理任务

Shutdown:不再接受新任务,但是能处理已添加的任务。调用shutdown()后,由Running--->shutdown

stop:不接受,不处理,所有处理任务停止。  调用shutdownNow()后,由Running/shutdown --->stop

Tidying:空闲。所有任务终止的时候。tidying后,会执行terminated()函数,这个函数可以自己去重载实现。

阻塞队列/线程池执行任务空 --->tidying

Terminated:彻底终止。tidying后,执行完terminated就会变为terminated。

 

线程启动:

线程启动后会执行runWorker方法,方法会不断去调用getTask()方法获取任务。在方法中while循环判断当前任务是否为null,如果为null就去执行processWorkerExit方法移出线程。

这里关于线程保活:这个processWorkerExit方法里会再次使用addWorker创建一个null的线程替换异常最后finally的线程。。

这是为了处理线程执行过程中由于异常抛出,不会继续while而会执行finally退出while,所以会有一个抢救措施。注意这里的状态要是running和shutdown才会去新加一个线程,还会根据异常判断。

 

需要理解,什么时候任务会为null?
第一种情况:非running状态,且状态为stop/tidying/terminated 或队列为空

第二种情况:

boolean timeOuted=false;  超时的默认值,默认为false

boolean allowCoreThreadTimeOut默认是falase,代表核心线程是否会超时

boolean timed=allowCoreThreadTimeOut(默认flase当前核心线程不会超时)|| 当前线程是否大于核心线程

if((当前线程数大于线程池最大线程数||(timed&&当前线程是否超时))&&(工作线程不为空||任务队列为空) )

看图理解更深刻一些。

下面根据场景来分析取不出线程的情况:

假设此时的核心线程是4,最大线程是8。最开始4个线程,阻塞队列满了开始扩到8个。阻塞队列搞完又减少到核心线程4个。并且默认allowCoreThreadTimeOut=false,即核心线程不超时,否则核心线程超时就直接回收会让逻辑不清晰。

1:不调用shutdown,runing执行完全部线程。讨论核心线程默认不设置超时时间的情况

由于是shutdown,第一种情况不考虑。

第二种情况下。timeout刚开始是flase,当前线程数是8个,大于核心线程数4个,所以timed= true。

第二条件是:((当前线程数大于线程池最大线程数||(timed&&当前线程是否超时))&&(工作线程不为空||任务队列为空),      timed&&timeout为false    同时当前线程数8不大于线程池最大线程数8(即线程数达到最大)

第二条件false。

进入try代码块,由于timed= true,调用workQueue.poll方法进行超时限制,没有在KeepAliveTime内取到数据,返回null。退出getTask方法,由于是null,不再在addWorker方法中while循环,执行finally的扣线程数操作。

(非核心线程数的回收,因为进到这里,timed=true 当前线程大于核心线程数,也就一定有非核心线程 )

           timed = false,通过take方法, 如果阻塞队列为空,当前线程会被挂起等待;当队列中有任务加入时,线程被唤醒,take 方法返回任务;

注意,如果队列是空的花,这里poll和take都会阻塞从队列拿任务,如果为空返回null,让线程池一直存活。

最后设置timeout的状态为超时。

由于这个方法是死循环for(;;),所以会进第二次循环,第二次循环,timeout=true,timed = true,且队列为空。CAS减少线程防并发,成功返回null,否则continue重新来过。

 

2.shutdown

发送中断信号给所有的空闲线程。

发中断信号前,判断当前线程是否中断,以及要获得工作线程的独占锁。即让当前任务执行完毕后,再发送中断信号。最终仍然会进入gettask方法,返回null后调用线程回收的方法进行回收。

具体的流程如下:

 

 

线程池为什么要有阻塞队列?

线程池创建线程需要获取mainlock这个全局锁,影响并发效率,阻塞队列可以很好的缓冲。

另外一方面,如果新任务的到达速率超过了线程池的处理速率,那么新到来的请求将累加起来,这样的话将耗尽资源。

 

为什么避免用Executors静态线程池?

因为Executors的底层使用LinkedBlockingQueue ,是一个无界阻塞队列,可以不断往队列加数据,有OOM的风险。如果一定要使用,要初始化大小。

 

Thread.yield() 方法的作用:

Thread.yield() 方法,使当前线程由执行状态,变成为就绪状态,让出cpu时间,在下一个线程执行时候,此线程有可能被执行,也有可能没有被执行。

sleep():使线程休眠指定的时间,该方法不会释放当前线程的monitor锁。

yield():提醒cpu当前的线程愿意释放当前的CPU资源,如果CPU资源不紧张,CPU会忽略掉这个提醒,如果CPU没有忽略这个提醒,然后将该线程的状态更新为RUNNABLE

posted @   NobodyHero  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
点击右上角即可分享
微信分享提示