决战圣地玛丽乔亚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
。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)