juc源码解读二(线程池)

线程池的状态

五种状态:RUNNING,SHUTDOWN,STOP,TIDYING,TERMINATED;SHUTDOWN状态下不接受新任务,但会处理队列中有的任务,STOP状态下不接受新任务,中断正在进行的任务,不处理队列中已有的任务;另外调用shutdown方法会进入SHUTDOWN状态,调用shutdownNow方法会进入STOP状态,区别在于shutdown方法中会遍历线程池set集合,判断Worker实例是否处于加锁状态,若是的,则表示该worker线程正在运行中,不会中断,而shutdownNow方法中会立即中断,不会进行事先判断;

线程池的执行流程

总结了6个主要方法

ThreadPoolExecutor#execute方法

一般的流程

先看是否达到核心线程数,没有则添加核心线程,达到了,则往任务队列中添加任务,任务队列满了,则看是否达到了最大线程数,没有则添加非核心线程数,达到了,则执行拒绝策略。

加入池状态判断的流程

其实还需要两次池的状态判断,这个属于细节。
一次是在已经达到了核心线程数或者添加核心线程数失败时后,需要判断池是否处于running状态,若不是,则需要提前添加带任务的非核心线程,此时肯定会执行拒绝策略,若是,则入队列。
另一次是入队列成功后,会判断池是否处于running状态,若不是,则尝试着从队列中移除任务,若移除成功,说明该任务在提交之后,该任务一直未被线程处理,则此时执行拒绝策略;若移除失败说明该任务在提交之后,在线程池状态变为非RUNNING之前,就被池中线程处理了,此时再次判断池中线程数是否为0,若是,则添加一个不带任务的线程。

ThreadPoolExecutor#addWorker方法

1)创建线程池中的线程

线程池中每个线程由Worker类管理,Worker类继承AQS,实现了Runnable接口,这使得Worker类实例对象可以看做是一个可以被执行的任务,而Woker类中又封装了Thread与Runnable作为实例变量,这使得每个Worker类实例对象既可以管理一个线程,也可以管理一个任务。之所以要继承AQS,是因为每个worker线程执行之前都会加锁(这段话有问题,加锁和是否继承AQS有关系吗,感觉没有);这个地方之前没理解(就是括号中的话),现在理解了,这涉及到了线程池中用到的锁机制;

第一把锁是每个ThreadPoolExecutor实例都有一个ReentrantLock属性;
线程池用set集合表示,非线程安全的,所以每个ThreadPoolExecutor实例都有一个ReentrantLock属性,一方面在往set中添加元素时,会加锁保证线程安全,另一方面对一些变量的操作也需要加锁(用volatile修饰某些变量会出现不精确,加锁可以保证串行化,结果更精确);另外在shutdown方法中,会遍历set集合,中断线程,采用加锁,保证每次只允许一个线程进行遍历执行中断,避免中断风暴(另一个线程重复中断正在中断的线程);如果用线程安全的set集合,在执行shutdown时不会加锁,此时多线程执行shutdown方法时,会产生中断风暴;
第二把锁是每个Worker类继承了AQS而重写了tryLock方法,改成了非重入的,在自带任务或从任务队列中拿到任务执行任务时,会先加Worker类自定义的锁,这样当执行shutdown方法时,会先执行tryLock方法,判断线程是否正在占锁执行,若是的,则不能中断正在执行任务的线程;如果是重入锁,就会把正在执行任务的线程也给中断了;另外为防止实例化Worker时,会设置当前Worker实例的AQS中的state为-1,这样不会导致刚加进set集合,就被其他线程遍历set集合后,执行了中断,而是要到runWorker方法中执行任务前,才先走unlock方法,允许被中断,再进入while循环,取任务,加锁,执行任务;
详细参考
https://blog.csdn.net/m0_74931226/article/details/128499940

https://blog.csdn.net/qq_14926159/article/details/126586289

2)往线程池中添加线程并且启动它

线程池就是一个存储Worker类实例对象的HashSet集合,每次添加成功后,会启动已添加的线程(拿到Thread属性,执行start方法)。返回true表示创建worker成功,且线程启动,返回false表示创建失败。
返回false的几种情况:
2.1)线程池状态为STOP或 TIDYING 或TERMINATION;
2.2)线程池状态为SHUTDOWN,但提交的任务不为空,或者线程池状态为SHUTDOWN,提交的任务为空,但任务队列中已经没有任务了;线程池在SHUTDOWN下,只存在一种情况可以创建线程成功,就是提交的任务为空,并且任务队列中还有任务;
2.3)线程池中线程数已达到最大核心线程数或已达到最大线程数;

ThreadPoolExecutor#runWorker方法

启动worker线程后,会执行Worker#run方法,其中又会执行runWorker方法,这里有几点需要注意

1)异常线程的退出;

用变量completedAbruptly控制,默认为true,但正常执行时,最后会置为false,否则一直为true,作为入参执行procssWorkerExit方法;因为如果是异常情况,则在processWorkerExit方法中会直接添加一个不带任务的以最大线程数为上限的线程,而不用考虑细节;

2)执行任务的优先顺序;

当前线程若带有任务,则优先执行,否则从任务队列中取,即执行getTask方法;并且是一个while循环,退出条件是不带有任务且getTask返回null;

3)线程执行任务前会加独占锁;

shutdown时会判断当前worker状态,根据独占锁是否空闲来判断当前worker是否正在工作。

4)运行任务,释放锁以及线程移除工作

执行task.run方法,接着释放锁,再次循环;退出循环后,会执行processWorkerExit方法,将当前线程从池中移除掉,若池的状态达到了终结要求,则进一步唤醒其他阻塞线程,全部退出;
其他方面需注意的:
会执行task.run方法,任务执行时直接把Runnable的run方法当做普通方法调用了,其中写的是用户自定义逻辑;(线程可以在getTask方法中阻塞,即调用prestartCoreThread方法,往池中添加不带任务的核心线程,该方法的注释写的是启动核心线程,使其空闲等待工作,这将覆盖仅在执行新任务时启动核心线程的默认策略,如果所有核心线程都已启动,则此方法将返回false);
在这里插入图片描述

这一段代码为了测试prestartCoreThread方法可以添加空闲线程,一直阻塞在队列的take方法处。所以tryTerminate方法中并不会清除这种线程池为RUNNING状态下的线程,只有当线程池变为STOP或SHUTDOWN且队列为空时,这些空闲线程才会被唤醒从而退出线程池,否则会一直阻塞;
维护线程池
将超过池阈值的线程或空闲线程从池从移除,当提交的任务和从任务队列中取出的任务为都为空时,当前线程需要退出线程池,会执行processWorkerExit方法,该方法主要是将空闲线程或多余线程从set集合中移除,其中又会执行tryTerminate()方法

ThreadPoolExecutor#getTask方法

是个自旋方法,主要是判断池状态以及根据核心线程是否被回收的设置,选择是否采用超时阻塞从队列中取任务,以下几种情况下,getTask返回null,其实可以归为三类:池状态不对,poll方法超时,在take方法上被中断唤醒(由tryTerminate方法负责);
1)池处于STOP,TIDYING,TERMINATION之一,会执行一次线程数减1,再return null;池处于SHUTDOWN,且任务队列为空,会执行一次线程数减1,再return null;
2)poll方法超时返回null,当设置核心线程也可以被回收时,此时所有线程都是使用poll(xxx)这种带超时的方法从队列获取任务,当超时时间到了,会返回null;当核心线程不能被回收时,则只有大于核心线程数的线程执行poll(xxx)方法,则大于核心线程数的线程会拿不到任务会返回null;超时到了返回null是这样设计的,有个timedOut参数,默认为false,当超时后,被设置为了true,此时再次循环时,只有一种情况不能返回null,即只剩一个线程且队列不为null,其他情况均可cas减掉1个线程数,返回null结束getTask方法;大于最大线程数的线程会拿不到任务会返回null(会调用带超时的poll方法,超时后返回null);
3)被中断唤醒的情况,即阻塞在take方法上而被中断唤醒的线程也会返回null(此线程即为在tryTerminate方法中唤醒的线程,属于真正的需要被退出的空闲线程);
回到runWorker方法中,getTask返回null的线程会从线程池中正常退出,即执行processWorkerExit方法,等待从池中被剔除。

ThreadPoolExecutor#processWorkerExit方法

1)当线程为异常退出,则计数减1;
2)将当前Worker实例从set集合中移除(其实只要是getTask返回null的线程都会从池中移除掉);
3)执行tryTerminate方法,清理真正的空闲线程(只有状态不对下的线程才会去唤醒阻塞在take方法处的线程);
4)兜底方案,当线程池为RUNNING或SHUTDOWN时,若线程在执行task时出现了异常,则直接执行addWorker(null, false)添加一个线程;接着讨论无异常的情况,若设置了不保留核心线程数时,则在队列为空时,可以使线程池为空,在队列不为空时则至少要加一个线程至池中;若设置了保留核心线程数时,若小于阈值,则也走addWorker(null, false) 添加一个线程,保证不低于阈值;

ThreadPoolExecutor#tryTerminate方法

1)是个自旋方法,只有池状态为STOP或SHUTDOWN且队列为空时,才继续执行,否则跳出循环;
2)当判断为以上两种状态之一,且池中线程数量不为0时,执行中断空闲线程操作,所谓空闲线程是指阻塞在queue.take()或queue.poll()方法上的线程,此时会执行 interruptIdleWorkers方法,入参为true,表示本次只中断set集合中的一个,入参为false,则中断set集合中的所有线程,该方法主要是遍历set集合,执行线程的interrupt中断方法,使在getTask方法中阻塞在workQueue.poll()或workQueue.take()处的空闲线程被唤醒,返回null,而当前线程则退出tryTerminate()方法,而被唤醒的线程在runWorker方法中再次执行processWorkerExit方法,再次执行相同退出线程池逻辑。相当于多米诺骨牌效应。
3)getTask方法中阻塞的线程收到中断信号后被唤醒,继续往下执行,最后设置timedOut为true,重新自旋,由于此时池的状态一定为上边说的两种,所以会执行一次池中线程个数减1,返回null退出getTask方法,被唤醒的线程在runWorker方法中接着会执行processWorkerExit方法,判断若此时池中线程个数为0,则代表被唤醒的线程是最后一个线程,则依次更改池的状态为TIDYING至TERMINATED。

拒绝策略

1)AbortPolicy:直接抛异常
2)CallerRunsPolicy:用调用者所在线程执行任务
3)DiscardOldestPolicy:丢弃阻塞队列中最靠前的任务,并执行当前任务
4)DiscardPolicy:直接丢弃任务
也可以自己实现RejectedExecutionHandler接口,自定义拒绝策略,如记录日志,或持久化来不及处理的任务;

jdk提供的四个创建线程池的函数

https://blog.csdn.net/p812438109/article/details/103983356
https://zhuanlan.zhihu.com/p/373341671

java中线程的中断

https://www.cnblogs.com/w-wfy/p/6414801.html
interrupted方法和isInterrupted方法的区别
interrupted是静态方法,返回的是当前线程的中断状态。例如,如果当前线程被中断(没有抛出中断异常,否则中断状态就会被清除),你调用interrupted方法,第一次会返回true。然后,当前线程的中断状态被方法内部清除了。第二次调用时就会返回false。如果你刚开始一直调用isInterrupted,则会一直返回true,除非中间线程的中断状态被其他操作清除了。

interrupt方法是用于中断线程的,调用该方法的线程的状态将被置为"中断"状态。注意:线程中断仅仅是设置线程的中断状态位,不会停止线程。所以当一个线程处于中断状态时,如果再由wait、sleep以及join三个方法引起的阻塞,那么JVM会将线程的中断标志重新设置为false,并抛出一个InterruptedException异常,然后开发人员可以中断状态位“的本质作用-----就是程序员根据try-catch功能块捕捉jvm抛出的InterruptedException异常来做各种处理,比如如何退出线程。总之interrupt的作用就是需要用户自己去监视线程的状态位并做处理。”
如果并没有阻塞方法,则interrupt方法只是设置中断标志位为true;
https://www.cnblogs.com/w-wfy/p/6415005.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

orcharddd_real

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值