线程池
Posted java_wxid
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程池相关的知识,希望对你有一定的参考价值。
文章目录
底层运行原理
线程池就是控制运行的线程数量,处理过程中将任务放到队列,然后在线程创建后启动这些任务,如果线程数量超出了最大数量就排队等候,等其他线程执行完毕再从队列中取出任务执行。
线程池相当于银行网点,常驻核心数相当于今日当值窗口,线程池能够同时执行的最大线程数相当于银行所有的窗口,任务队列相当于银行的候客区,当今日当值窗口满了,多出来的客户去候客区等待,当候客区满了,银行加开窗口,候客区先来的客户去加班窗口,当银行所有的窗口满了,其他客户在候客区等待,同时拒绝其他客户进入银行。当用户少了,加班的窗口等待时间(相当于多余线程存活的时间)(等待时间的单位相当于unit参数)假设超过一个小时还是没有人来,就取消加班的窗口。
七大核心参数
底层在创建线程池的时候有七个参数:核心线程数,同时执行的最大线程数,多余线程存活时间,单位时间秒,任务队列,默认线程工厂,拒绝策略
maximumPoolsize:同时执行的最大线程数
keepAliveTime:多余线程存活时间,当前线程池数量超过核心线程数时,当前空闲时间达到多余线程存活时间的值的时候,多余空闲线程会被销毁到只剩核心线程数为止
unit:多余线程存活时间的单位
workQueue:任务队列,被提交但尚未被执行的任务
threadFactory:生成线程池的线程工厂
handler:拒绝策略,当队列满了并且工作线程数量大于线程池的最大线程数时,提供拒绝策略。
如何合理的配置核心线程数?
对于CPU密集型任务,由于CPU密集型任务的性质,导致CPU的使用率很高,如果线程池中的核心线程数量过多,会增加上下文切换的次数,带来额外的开销。因此,考虑到CPU密集型任务因为某些原因而暂停,这个时候有额外的线程能确保CPU这个时刻不会浪费,还可以增加一个CPU上下文切换。一般情况下:线程池的核心线程数量等于CPU核心数+1。例如需要大量的计算,视频渲染啊,仿真啊之类的。这个时候CPU就卯足了劲在运行,这个时候切换线程,反而浪费了切换的时间,效率不高。打个比方,你的大脑是CPU,你本来就在一本心思地写作业,多线程这时候就是要你写会作业,然后立刻敲一会代码,然后在P个图,然后在看个视频,然后再切换回作业。emmmm,过程中你还需要切换(收起来作业,拿出电脑,打开VS…)那你的作业怕是要写到挂科。这个时候你就该一门心思地写作业。
对于I/O密集型任务,由于I/O密集型任务CPU使用率并不是很高,可以让CPU在等待I/O操作的时去处理别的任务,充分利用CPU。一般情况下:线程的核心线程数等于2*CPU核心数。例如你需要陪小姐姐或者小哥哥聊天,还需要下载一个VS,还需要看博客。打个比方,小姐姐给你发消息了,回一下她,然后呢?她给你回消息肯定需要时间,这个时候你就可以搜索VS的网站,先下安装包,然后一看,哎呦,她还没给你回消息,然后看会自己的博客。小姐姐终于回你了,你回一下她,接着看我的博客,这就是类似于IO密集型。你可以在不同的“不烧脑”的工作之间切换,来达到更高的效率。而不是小姐姐不回我的信息,我就干等,啥都不干,就等,这个效率可想而知,也许,小姐姐根本就不会回复你。
对于混合型任务,由于包含2种类型的任务,故混合型任务的线程数与线程时间有关。在某种特定的情况下还可以将任务分为I/O密集型任务和CPU密集型任务,分别让不同的线程池去处理。一般情况下:线程池的核心线程数=(线程等待时间/线程CPU时间+1)*CPU核心数;
并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,我们的项目使用的时redis作为缓存(这类非关系型数据库还是挺好的)。增加服务器是第二步(一般政府项目的首先,因为不用对项目技术做大改动,求一个稳,但前提是资金充足),至于线程池的设置,设置参考 2 。最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件(任务时间过长的可以考虑拆分逻辑放入队列等操作)对任务进行拆分和解耦。
拒绝策略
第一种拒绝策略:AbortPolicy:超出最大线程数,直接抛出RejectedExecutionException异常阻止系统正常运行。可以感知到任务被拒绝了,于是你便可以根据业务逻辑选择重试或者放弃提交等策略。
第二种拒绝策略:该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,相当于当线程池无能力处理当前任务时,会将这个任务的执行权交予提交任务的线程来执行,也就是谁提交谁负责,从而降低新任务的流量。(谁调用了你,到达最大线程数时,你回去找调用你的人,然后听从调用你的人安排)(超出的我们能办的给你办,不能办的给你回退 )这样的话提交的任务就不会被丢弃而造成业务损失,如果任务比较耗时,那么这段时间内提交任务的线程也会处于忙碌状态而无法继续提交任务,这样也就减缓了任务的提交速度,这相当于一个负反馈,也有利于线程池中的线程来消化任务。这种策略算是最完善的相对于其他三个。
第三拒绝策略:DiscardOldestPolicy:抛弃队列中等待最久的任务,也就是它丢弃的是队列中的头节点,然后把当前任务加入队列中尝试再次提交当前任务。
第四种拒绝策略:DiscardPolicy:直接丢弃任务,不予任何处理也不抛异常,当任务提交时直接将刚提交的任务丢弃,而且不会给与任何提示通知。
实际创建线程池
java.util.concurrent 包里提供的 Executors 也可以用来创建线程池
- newSingleThreadExecutos 单线程线程池,也就是线程池只有一个任务,这个我偶尔用一用
- newFixedThreadPool(int nThreads) 固定大小线程的线程池
- newCachedThreadPool() 无界线程池,这个就是无论多少任务,都创建线程来运行,所以队列相当于没用。
在实际使用的时候,选择线程池的时候尽量不用JDK提供的三种常见的创建方式
第一是 Executors 提供的线程池使用场景很有限,一般场景很难用到
第二他们也都是通过 ThreadPoolExecutor 创建的线程池,我直接用 ThreadPoolExecutor 创建线程池,可以理解原理,灵活度更高。
第三因为它的底层队列是Linked这个接近于无界,非常大,这样会堆积大量的请求,从而导致OOM,阿里巴巴开发手册推荐我们使用ThreadPoolExecutor去创建线程池。
以上是关于线程池的主要内容,如果未能解决你的问题,请参考以下文章