00:00
好,接下来呢,我们就来详细的说一下线程池,首先我们在前边我们可以通过调用线程池的excute方法,我们给里边提交一个异步任务,咱们线程池呢就会来自动的来执行它,但是呢,我们提交之前,我们肯定得先由我们这个线程池,咱们先来说一下我们线程池的这个创建,我们如何创建出一个线程池,首先来看最快的方式,就是用我们以前的这个exuts工具类,它能帮我们快速的创建出我们指定形式的这个线程池。接下来第二种方式来看原生该怎么用第二种方式,那我们原生创建线程池的方式,我们可以这么来做好,我们把这个方法呢放在这,我们呢,线程池原生呢是一个这个们叫thread to execu好,这是我们说的这个线程池,它呢是一个thread po excu,这个excu呢,我们点过来,它来继承于这个,它是一个。
01:00
Executor service接口其实跟我们这个用executors工具类创的这个东西,我们来点进来看一下一样,都是excuor service,所以呢,我们说的这一块线程池指代的就是我们的这个execute service下边的东西,那好,我们现在呢,通过这种方式也可以得到一个线程池,我们就通过创建它,但是呢,创建它比较复杂,来点进来给大家看一下,我们发现呢,它会用到非常多的参数,那最完整的那就是最下边的这个,它呢会用到1234567,我们这七个参数,这也就是我们说的线程池的七大参数,那这七大参数都是什么意义,我们搞懂了才能把它用的非常清楚,好,我们来说一下它的这七大参数,七大参数。首先我们来看第一个,第一个我们这个里边有一个叫Co process,我们这个核心Co好,我们来复制过来,这个呢是我们这个核心线程数,核心线程数它是什么意思呢?我们来看这一块的解释,它是说我们这个线程的数量总是在我们这个池中保持的,就算我们这个系统当前是空闲的,我们也是在池中一直有着,就等待来接收新任务的,除非我们设置了这个参数,比如说允许我们这个核心线程超时,这样呢,核心线程空闲了,那就被回收了,否则呢,它就是不回收的意思呢,这就是我们核心线程数,也就指的是线程池创建好以后,好以后就准备就绪的线程数量,而这些线程数量呢,就等待,就等待来接收异步任务,异步,异步任务去来执行,异步任务去执行,大家就可以理解为。
02:51
我们线程池一创建出来,比如我设了一个五,那相当于呢,我们给线程池里边就拗了五个thread,哎,我们拗了五个thread对象,只不过呢,他们都没有start,只有start了,那才是我们这个开启线程了,我们new了五个thread,好,我们把这写一下,我们new了五个thread,那什么时候我们会启动呢?我们给线程池里边像之前一样提交一个任务以后,我们这个thread,诶空闲的thread拿过来就会呢,直接带着我们这个runabable来直接start,这就是我们说的这个核心线程数,这是第一个,而且呢,它是一直在的,我们来说一下这个一直存在核心线程数,只要线程池不销毁,一直存在,一直存在,除非我们设置了他说的这个,我们允许我们核心线程超时的这个属性,除非,哎,除非我们设置了它,这是我们说的第一个属性,核心线程池的大小。
03:50
核心大小,好,接下来我们再来看第二个,那么这个线程池里边第二个参数,它叫max,相当于我们池里边最大数量,诶这个就是我们这个最大线程数量,最大线程数量,那我们来看一下这一块的解释,它呢就是说我们这个池里边能允许的我们最大的这个线程,比如说我们现在最多有这么多的线程数,可能同时再来执行任务。
04:18
那它的作用其实就是我们来控制资源,并发的控制资源。不像以前一样,每一个异步任务一进来,直接new start start,这样资源呢就耗尽了,我们现在呢,设置了一个最大数量异步任务提交给线程池以后,比如我们设置了一个200,那就算呢,你提交了100万个异步任务,那我现在呢,当前也只有200个正在运行。所以呢,我们控制的这是最大数量,那还有一个我们来看一下它呢,有一个叫keep alivetime来这个呢,我们来看一下它,这就是我们说的存活时间,这个存活时间指的是什么?来看一下存活时间。存货时间来阅读一下这块的文档说明,他说当我们这个线程数量大于核心数的时候,哎,相当于我们当前,如果如果当前正在运行的线程数量,正在运行的线程数量大于我们这个核心数量,好,核心数量他也没说正在运行,那就是当前我们这个线程数量大于我们这个核心数量,但核心数量以后,接下来他怎么办呢?来看一下他说。
05:31
这就是我们的最大时间什么的,最大时间它是来我们这些空闲线程来等待我们来接收新任务,到我们中断它之前,也就是我们准备把它断了,相当于把这个线程呢释放了,到它接收新任务的时间,那这个时间就是相当于我的这个线程空在这儿没用了,任务都处理完了,我先浪费线程池的资源,那就等待我们指定的这个时间,比如我们说的十秒等待十秒才没有新任务给我到来,那我就把它中断了,相当于我们就释放了,所以呢,这个的作用就是它会释放我们这个空闲的线程,那什么时候释放呢?就是只要只要我们这个线程。
06:18
空闲大于我们指定的存活时间,相当于我们像县城已经闲了这么久了,没人用了,那么就应该把它释放,而且释放的是什么?大家注意啊,因为我们这个核心大小它是一直存在的,它不可能得到释放,即使它咸了,我们看刚才的注释说它就算闲着也一直存在,所以呢,我们释放的,释放的这个线程指的是我们最大的大小减去核心的大小,相当于我们超了核心大小的这一部分。这一部分呢,才是需要释放的,就是说他说的。大于我们核心线程的这些数,我们是要进行释放的,那核心线程呢是不释放的,这是我们的存活时间,那这个时间到底是多久呢?是以什么为单位来接下来下边它呢,就可以指定一个unit,诶就是这个就是我们说的时间单位,核心大小最大我们这个时间还有时间单位好我们在这呢,可以来指定我们的时间单位,时间单位好这个完了以后呢,接下来我们来继续看,下面呢还有一个东西叫broken queen,里边呢传了一个runnerable,好把这个复制过来,把签名最后拿过来来看一下这个block queen呢,我们称为叫阻塞队列,那阻塞队列的意义是什么?们来看一块这一恰的我们来看一下这一块的解释,他呢说这个work坑qui,那我们这个队列呢,它是用来相当于保存,保持我们这个任务在他执行之前,相当于如果我们提交了。
07:59
很多任务,这些任务呢,就会放到队列里边,先临时保存着,这是我们说过的,它的作用就是呢,如果如果任务有很多,如果任务有很多,因为现在看到我们最大呢,只允许200,假设现在1000个过来了,那还多了八九百来,那这800个任务呢,就会将目前多的任务,多的任务然后呢,放在咱们这个队列里边,队列里边,那放到队列里边以后怎么办呢?那接下来只要有空闲的线程,只要有空闲的线程,那它就会从队列里边再去新的任务执行,只要有线程空闲了,现成空闲就会去队列里边,这个队列相当于一个容器,保存东西的,就会去队列里边取出新的任务,新的任务继续执行。
08:53
这是我们说的这个阻塞队列,那还有一个我们来看一下阻塞队列完了以后呢,这还有一个叫th factory,相当于我们创建现成的这个工厂,它是如何new一个thread的,那我们这个工厂啊,这是现成的创建工厂,这个工厂呢,那都是默认的来,我们可以放在这工厂,当然大家也可以自定义,比如我们每次自己来创建线程的时候,我们线程的名字呢,我们有自己的一个约束,那么就可以自己来写一个线程的创建工厂,然后呢,接下来还有一个叫handler,这个handler是干什么的?再来看一下。
09:33
那这个handleler他来解释说,我们这个handleler它是用来执行,执行什么呢?执行我们被阻塞的什么,被阻塞他说是由于们当前这个线程,我们整个队列的容量都已经满了,它来处理这种情况,相当于它来处理我们这个队列满了以后怎么办好,我们这个看到的作用就是如果如果我们这个队列买了,我们这个队列买了,相当于我们存不了更多的现成任务了,那么接下来怎么办?那这个呢,就是我们来看一下他传的参数,这个参数呢叫reject,我们拒绝的执行处理器,所以呢,他接下来要做的就是如果队列满了。
10:18
按照我们指定的,我们指定的拒绝策略。拒绝执行我们的任务,比如拒绝执行任务,所以呢,这是我们说的这七大参数,但是呢,我们现在得搞清楚他们的整个工作顺序,怎么样搞清楚这个工作顺序呢?因为现在这个我们发现呢,它有一个这个最大的大小和核心的大小,还有我们这个队列排队,那什么时候都能用到它们。我们来说一下流程,这个流程呢,我也给大家总结到我们的这个课件里边,流程呢是这样子的,好,首先我们这个线程池如果创建好,我们这个线程池只要创建起来,那就会准备好我们指定的核心数量的这些核心线程等待来接受任务,这我们的第一步,比如我们这个核心数量传了一个五,那就相当于我把其他呢先来置为,那好,我们把其他先来置为,那我们来一个一个传。
11:22
还有那。我们来看一下我们的效果,那如果我们传了一个五,相当于我们这个线程池一创建起来,它又准备了五个空闲线程,准备接收任务,那接下来只要有任务进来了,那他就会利用空闲的线程来执行,那接下来假设我们这五个线程都买了,咋办?好接下来就是我们第二步,如果我们这个核心线程数满了,那呢,它就会将我们再进来的任务先会放到我们这个阻塞队列里边,那空闲的线程呢,就直接会去阻塞队列里边来获取我们这个人物。
12:01
所以我们来说第二个1.1,那如果我们这个核心线程满了,但来了很多任务,那放哪呢?会先放到队列里边,只要我们核心线程有空闲,那他呢就去队列里边拿到任务去执行,接下来我们第二个如果我们这个阻塞队列也满了怎么办?我们说如果阻塞队列满了,那我们就直接再来开新线程继续执行,但我们这个新线程啊,最大只能开到我们指定的这个数量,我们可以来看一下,我们呢在这儿还指定了一个max max,比如我写一个200,那就是呢我们这个线程池。最多能开200个线程同时执行。所以我们核心线程数不够了,先给队列里边放好,接下来继续,如果队列满了,我们就直接开任务,但是呢,最多不能开到我们这个max指定的数量,那如果我们这个max都满了怎么办?那我们说如果max满了,那我们就会使用我们的拒绝策略来拒绝接受我们这个任务。
13:07
但是如果max没买,假设呢,Max现在是200,我们全都执行成功了,也没有新的任务进来了,空闲了,好我们先来看,Max买了就用拒绝策略,就用我们这个来拒绝任务,但是呢,如果我们这个max没买,Max都执行完成。家人呢,有很多空闲了,有很多空闲。我们核心是五,Max是200核心的,先放在这,相当于比核心大了195,假如这195呢,都空闲了,那怎么办呢?接下来就会在指定的keep alivetime,就是在指定的时间,在我们这个指定的时间以后,在指定的时间以后,就是我们的这个存活时间以后,那么就会释放,释放max减我们这个空这些线程。
14:06
相呢呢,这是一个可供伸缩的我们这个线程池,最大标到我们200,最小呢是五,所以呢,我们要创建线,使用线程池,一定要搞清楚那们这个核心最大,特别是我们这还有一个阻塞队列,它们之间的关系来keep of time,假设我们来写一个十,我让他时间呢写一个time unit是十秒,十秒没有新任务我们就给它释放,这呢work困相当于我们这个阻塞队列,那阻塞队列呢,它需要我们这个类型,这是一个接口来CTRLH,它有各种不同形式的我们这个队列,我们来使用一种阻塞队列就行了,比如我们就来使用我们的这个叫link的broken queen,好,我们就在这来传一个阻塞队列,我们如果自己来创建的话,我在这来传一个new,一个link的block qui,但这个传的时候大家一定注意,它的这个容量呢,是配的最大值,默认是配最大值。
15:05
来说一下,它这块呢,还有一个小细节,就是说它默认默认。默认是咱们这个应配置的最大值,相当于我们能保存这么多的任务,但这呢,可能就会导致我们这个内存不够,把我们这个内存呢直接占满,我们只要有异步任务进来,直接给阻塞队列里边保存保存,把内存呢最终占满,导致我们其他服务都不能正常进行的。所以呢,我们的这个link的block困一定要传入我们业务定制的数量,这个数量呢可以我们做压力测试,得到系统峰值以后能给他定的一个数量,比如我们现在最多允许我们这个当前系统里边就存10万个,那么就一定给他指定上数量,还有我们说的这个thread factory,我们创建现成用的工厂,这个工厂呢,我们也可以用默认的,它的作用我们能看到,就是把我们的一个R,我们的runable最终呢,包装成一个thread对象,我们最终要启动它才启动线程的,那么这个线程工厂呢,我们就可以用默认的这个线程工厂CTRLH我们来看。
16:11
跟它的实现当里边其他包下呢,有很多,主要呢是我们这个executs jc包下有我们这个默认的工厂,所以呢,我们在这儿创建的时候,我们就可以来使用我们的默认工程,比如new,一个default叫thread factory,这个thread factory呢没法扭过来,那我们就还是使用exuts工具类,它呢有一个default。默认的县程工厂,如果大家对这个默认的县程工厂不满意,我们来点进来,他在这创建呢,相当于每一个县城的名字都会加一些前缀啥的,大家对这个不满意,可以自己来写一个线程工厂,那么就来直接使用默认的。还有我们这个handle德ler叫拒绝策略,那不同的拒绝策略呢,最终会达到不同的效果,我们可以先来看一下我们这个拒绝策略有多少CTRLH我们看到呢?这有非常多的,主要呢来看JC这个包下的JCJC好,我们默认的拒绝策略呢,有这么几种,第一个什么discard all,相当于呢,丢弃我们最老的任务,现在如果我们有新任务进来了,把我们旧的任务还没执行的丢弃掉。还有我们这个叫color runs policy,那相当于直接来调用run方法的这个策略,这个策略呢,就是咱们这个异步任务进来了,本来我们要启动线程给你执行的,但是内存不够了,哎,我们这个资源控制住了,最大的也满了,队列里边也满了,那我呢就直接调用你的让方法,我也。
17:34
不给你启动线程了,现在做了一个同步调用,这是这种策略,你的方法得到了调用,还有呢,我们这个abert直接来丢弃,相当于你这个新来的任务直接给你丢了不要了,那还有呢,我们这个discard这个呢其实也是丢弃,但这两种的区别就是这个把任务丢弃了,还抛异常,这个丢弃了呢,就不抛异常了,而这两个丢弃呢,不同的是他丢的是当前任务,你心进来了,满了,没位置了,把你丢掉,而他丢的是。
18:05
我们老的任务相当于我们这个队列里边对头的,那就是最老的,把它给丢弃了,这是我们说的拒绝策略,当然拒绝策略呢,我们可以来看一下它默认的拒绝策略是什么?好,我们来到这个thread pocu里边,如果我们使用这种构造器。他默认用这个default hand了,他是用这个丢弃策略,先直接把我们这个任务给它丢掉,那么呢,也可以来选用这个策略,比如来扭一个我们的这个策略,当然这个没法new使用thread to execute里边的这种丢弃策略好来创建出它,这就是我们说的线程池的七大参数,合理用好这些参数,理解我们这些工作原理,合理的使用到我们的线程池。那着重理解我们这个核心线程数量,以及我们这个最大数量跟队列的关系,正好呢?比如我们来举一个面试的例子来,如果呢,有一个面试官来这么来问,说我们一个线程池核心是七,最大二十,队列呢只允许存50个,100个并发进来,我们这个当前是怎么分配的?
19:17
首先100个进来,第一个七个立即执行,七个会立即得到执行,因为我们这个核心里边七七个空闲的就等待任务来,然后呢,接下来我们更多任务要先直接进入队列,而不是来开线程执行的,所以呢,我们现在七个会得到立即执行,然后呢,由于我们队列只有50个,所以呢,50个会进入队列,那进入队列以后呢,接下来队列满了,我们还应该有新的并发进来,那我们相当于能多开线程了,那多开线程呢,相当于我们再能最多开13个,相当于我们呢再开13个进行执行。那现在呢,相当于我们来总计队列里边50个核心七个,因为我们最大二十个,现在再开只能开13个,现在我们总共有20加50个已经安排上了,那我们剩下30个没有安排上,所以呢,剩下的30个,30个就使用拒绝策略来执行,就使用咱们这个拒绝策略。
20:25
那这个拒绝策略呢,一般都是丢弃,我们刚才也看到有别的方式的这个拒绝策略,CTRLN这个拒绝策略里边,但也有可以直接不异步执行的,就调用它的color run这个拒绝策略。这个拒绝策略它的方式就是只要我们的这个TH铺线程池没关,我们来看它只要线程池没关,它呢直接会调用我们这个runable的run方法,相当于呢,直接调用了我们以前。Runable里边的这个wrong,我们说这个runable的run这个调用,这是一个同步调用,想要异步得把它new thread让它start的方式,所以呢,我们也可以使用不抛弃的方式,如果不想抛弃,如果不想抛弃还要执行。
21:14
那我们可以来使用这种策略来,我们让它呢,以同步的方式自己直接来执行一下,比如把它复制过来,或者呢丢弃最老的,把那些一直得不到执行的给它丢弃掉,好,这是我们说的这种好,这就是我们说的线程尺,我们用最原生的方式可以直接拗出来,当然我们呢,也可以使用executors帮我们快速的创建出这线程池,而executors里边呢,有几种我们常见的这几个线程池,大家呢,来了解一下,首先呢,Executors可以创一个叫catch的spread po,哎,我们可以来看一下exs点有一个new一个can的thread铺好那点一个new一个can的,这个相当于带缓存的我们这个线程池,它可以灵活回收我们的空闲线程,比如我们来进入它的这个源码里边,我们来点进来,因为它的核心数量是零的,所以呢,相当于没有。
22:15
核心的空闲线程保持在这儿,当然我们可以创建非常多的线程,但只要这些线程一空闲,那最终呢,都会被清空,清空到零,正如说的第一种。那第二种是我们见到的这个方式,好把这个这是我们第一种,第二种我们来看exs里边呢,我们还可以拗一个我们叫fix的thread铺,我们固定池大小的点进来,我们可以来看一下自己呢,可以传一个固定值大小,这个呢相当于我们的核心大小,核心这么多,最大也这么多,相当于这个线程池里边呢,就只有十个线程。多了也是十个,少了也是这十个,而且他们一直是存活的,这是我们说的第二种方式,固定限制大小的好,这个呢是固定大小,固定大小这个呢是核心是零,核心是零,所有都可以回收的,所有都可回收。
23:12
而这个固定大小呢,是核心等于最大的,相当于呢,都不可回收,都不可回收,就一直拿这几个线程来执行所有,那我们还有exuts点一个叫new,一个schedule,我们的这schedule呢,是任务调度的意思,我们来点进来看一下。它里边呢,还能传入我们这个核心大小,只不过呢,它创建的是我们这个schedule,我们的这个excu,这个excu再来执行任务的时候,我们来看一下,它可以呢,给里边提交一些任务,来指定多长时间以后再来执行,现在我们这个S是专门来做定时任务的线程池,定时任务的线程池。好,这是我们的这个,那还有我们这个里边来看一下,点一个,你有一个叫single,这叫单线程的这个线程池,来点进来看一下,这个核心跟最大都只有一个队列呢,是一个无界队列,有很多,相当于我们这个一个线程从队列别拿一个执行一个,拿一个执行一个,相当于我们后台用单线程的方式,一个一个保证任务顺序的执行,好就是这个,诶这个是单线程的线程池。
24:33
后台从队列里边获取到任务,从队列里边获取任务。里边获取任务,挨个执行,这是我们说的我们常用的这几种线程池。
我来说两句