java并发笔记之Executor框架线程池源码解析

目录

前言

1、ThreadPoolExecutor

1.1、来源

1.2、类关系

1.3、构造器

1.4、executor方法

2、newFixedThreadPool

3、newCachedThreadPool

4、newSingerThreadExecutor

5、newScheduledThreadPool

5.1、RunnableScheduledFuture

        5.1.1、来源

        5.1.2、类关系

5.2、DelayedWorkQueue

        5.2.1、来源

        5.2.2、类关系

        5.2.3、构造方法

        5.2.4、offer方法

        5.2.4、take方法

5.2、ScheduleThreadPoolExecutor

        5.1.1、构造器

        5.1.2、executor方法

6、Worker

6.1、来源

6.2、类关系

6.3、构造器

6.4、run方法

6.5、AQS相关方法

6.5、runWorker方法


前言

有关Executor框架下的四大线程池源码理解,主要是创建工厂,和工作线程插入时候,源码运作。这里大致给出几种线程池的大致描述,援引自《java concurrency in pratice》

1、newFixedThreadPool:混合线程池,创建一个固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程池的规模将不再变化(如果某个线程发生了未预期的Exception而结束,那么线程池会补充一个新的线程)

2、newCachedThreadPool:缓存线程池,创建一个可缓存的线程池,如果线程池的处理规模超过了当前处理需求时,那么将回收空闲的线程,而当需求增加时,添加新的线程,线程池的规模不存在任何限制。

3、newSingerThreadExecutor:单例线程执行者,是一个单线程的executor,它创建单个工作者线程来执行任务,如果这个线程异常结束,则会创建另一个线程来替代。同时他也能依然队列中任务的顺序来执行任务

4、newScheduledThreadPool:创建一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。

并且,有关这几个线程池,我会采用关注创建工厂,以及executor方法还有列出父子关系等方式来观察分析他们的源码内容,而且,我们通过简单查看他们的构造工厂发现,其实他们都是实例化一个类ThreadPoolExecutor,他们与这个类实现密切,所以,文章会单独拧出这个类出来进行源码讲解

 

 

 

 

 

 

 

1、ThreadPoolExecutor

没看过源码的会觉得,这个类觉得陌生,那么请接着看

 

 

 

 

1.1、来源

话不多说,上四大常用线程池的构造工厂:

ThreadPoolExecutor同时满足四大常用线程池的要求,简单来说,他是这四个的爸爸

 

 

 

 

 

 

 

 

1.2、类关系

他继承自抽象类AbstractExecutorService,该抽象类没有抽象方法的,因此也能明白,它是概念上的抽象,文字上的抽象,而非一种代码上必要的抽象关系。

而AbstractExecutorService则是接口ExecutorService的实现,接口中有很多抽象方法

ExecutorService接口继承自Executor,而executor大家很熟悉了,就不多做介绍了

 

 

 

 

 

1.3、构造器

他的构造器有四个:

其实没什么特殊,都是调用参数最完整那个。而最少也要给出如下几个参数

最多则有那么多,也能看到可选的就是异常处理,还有线程工厂,由此多延伸出来三个构造器,只有异常处理或是只有线程工厂,以及两个都有的情况。也能看出,构造器并没有办什么实际的事儿,他就是把参数记录一下,

然后先来看看各个必须参数代表的含义

corePoolSize:默认线程池大小

maximumPoolSize:线程池可扩张的最大大小

keepAliveTime:官方解释是:Timeout in nanoseconds for idle threads waiting for work,解释一下,就是当一个新的线程进来,我们的线程池满了,不论是等待线程池扩张的时间,还是线程池到了最大值无法扩张,等待工作线程结束让出空位的时间,这两种时间都叫keepAliveTime

unit:这个unit,这里其实蛮尴尬的,只是一个用来转换keepAliveTime的工具用什么而已,从下图也能看出,并不实际存他

workQueue:顾名思义,他是用来管理工作线程的队列,其实现必须是阻塞队列

 

 

然后再来看看可选参数threadFactory,只是一个接口,要求具体实现,

来看看默认线程工厂的实现,defaultThreadFactory,是Executors工具类的一个内部类,请接着往下看他干了什么做什么的?

关于工厂DefaultThreadFactory,他有一个newThread方法,是一个工厂,专门负责创建我们当前线程池所需线程的,然后先注意一下这个工厂的构造器,他的group是取自Thread中成员变量group,用来标记每个线程属于哪个group的。

顺带一提newThread,会规范的生产线程,给定当前group,并且按照自己的规范取名。注明生产出的线程是非守护线程,以及默认优先级为5

 

 

 

然后再来看看可选参数handel,他直接用了类加载时准备阶段生成的AbortPolicy,关于这个类,后文会用到,暂时看看

他是ThreadPoolExecutor的一个内部类,其实现,只是为了抛异常,抛出规范化的异常,不会影响线程池运作的异常

 

 

 

 

 

1.4、executor方法

代码如下,首先粗略的看一下,根据方法名参数不难看出什么意思,先把大致代码意思说一下把,获取当前工作队列的长度,小于默认大小就直接插入。否则,就是超出默认大小,或者没有插入成功,对于超出默认大小的,第二个if保证,延时阻塞插入,延时插入能成功的话,判断有没有运行不正常的节点,不正常的移除,抛出异常。而当没有插入成功时,最后一个if保证,再试一次,还不行,ok抛出异常了。

如下几个方法源码是关键:ctl、workCountOf()方法、addWorker()、isRunning()、reject()、remove(),下面会一一作出解释

首先是ctl,只是一个原子的状态计数值,所以ctl.get()只是原子的获取一个状态值放入线程变量区,关于ctl的算法想要看明白,建议去刷《hackers delight》这里理解成神秘的算法,满足简单count++或是count--即可

关于workCountOf、reject、isRunning如下,实现如名字般简单容易理解,reject是说明,非运行状态下,无法从队列移除,那么就抛出异常。

remove方法:直截了当的移除,关于try方法这里就不截图做详细说明了,后面注释可以了解,当前为空时并且造成了关闭的处理,博主进去看了,很长,大概意思就是将全局阻塞的线程全部激活。

addWaiter方法在这里比较核心。他比较长,这里分段截图说明。

第一段自旋,主要是将workQueue的异常、正常状态,以某种算法和ctl联系在一起,异常下ctl为几,正常情况下ctl为几,然后需要注意一下的是core参数,图中wc >= (core ? corePoolSize : maximumPoolSize),也就是说这个参数是用来判断添加工作者的时候,ture就是用默认值判断大小,false就是用最大值判断大小,前文executor源码中有多处addWorker,后面的第二个参数有ture有false,看完这里,就能很好的明白逻辑了,当使用core添加失败,就用max试试,个人认为,这样设计不好,为了解耦,而让代码运行更加繁复,有点本末倒置了

 

随后这一段的数据准备,需要了解下Worker是什么。他是个满足了AQS的一种线程,ThreadPoolExecutor的一个内部类,这个类呢,就是我们常说的线程池中的工作线程,构造器传入一个线程工作,拓展了几种方法,设计模式上而言,是一种组合模式,也可以叫做装饰器模式,实际操作的还是构造器传入线程,

话题回到之前介绍的addWorker方法。一下是后续的所有代码,将线程传入工作线程,后面的代码也是一目了然,加上全局锁,将工作线程添加进工作线程。这里有个疑问,为什么要加全局锁,阻塞队列中add操作是线程安全的操作,其实,只是为了把添加操作和其他状态判断抛出异常处理给原子化。

然后是添加完毕后再判断,队列有没有超出设定,没有超出最大值的话,start启动。超出的话则使用失败处理addWorkerFailed,这里需要留意,线程池的线程数量的控制,并非使用阻塞队列的有界性抛出异常实现的,而是另外用了个计数器。

然后我们来看看超出线程池的部分怎么样?

移除,并且设置状态值ctl,而tryTerminate方法前面也提到过,当前为空时并且造成了关闭的处理。

看完这里,会产生一个疑问,那么如果超出线程队列默认大小时,又没达到最大怎么办?怎么扩容,我们忽略了一点,各个工具是分工合作得,我们这里采用了自身变量去记录队列大小,实际上,他是一个blockingQueue接口,具体实现,可能是Array的,可能时linked,也可能优先级、同步队列等等等,每个实际不同的实现一开始初始默认值本来就不同,扩容也不同,我们这个类关注的是线程交给工作线程去处理,以及管理线程的一些功能。既然选择了BlockingQueue,那么关于容量处理,完全可以放心的交给他去做。

 

 

 

 

 

2、newFixedThreadPool

混合线程池的构建如下图,他是依靠ThreadPoolExecutor中,默认大小和最大大小去固化线程池容量的。队列选择,LinkedBlockinQueue,默认大小Integer.MaxValue()。毕竟参数要求int,也不会有范围方面的错什么大小超过队列大小啥的,

 

 

 

 

 

 

 

 

 

3、newCachedThreadPool

不用传入参数,默认0,最大值Interger.MAX_VALUE,这跟之前不同是keepAliveTime不为0了,但是,考虑到keepAliveTime的官方解释,这里又是SynchronousQueue的链表实现,这玩意在这里,好像没啥用,而且博主看到这里也纳闷,返回去再看一下executor里面,也没用到这个参数,再结合缓存线程池的概念,吐槽一下,这参数给的,纯属好看。工作队列是SynchronousQueue实现的,关于这个容器的源码细节,可以参考我另外一篇博客https://blog.csdn.net/m0_37590135/article/details/101771074

其实关于介绍中说的Cache,解读了源码,我们可以发现,完全就是在唬人,说什么满了会回收没在运行线程,这玩意也不是他特有,fixed线程池同样也有,相信大家项目中也遇到过,面对甲方,产品,leader,或是造风产品会啥的,面对一个完全说不出优点的玩意,那就把大家都有的特点拿出来说一说造造势,也可以理解。

他与fixed线程池相比的唯一优点,可能就是阻塞队列的选择不同,Synchronous,完全采用CAS实现的Put、take,和linkedBlockingQueue对比,Linked是采用读写锁的分离形式。如果说优点,只是某些情况下Synchronous性能高于Linked,而另一些情况下相反

 

 

 

 

 

 

 

 

 

 

 

 

4、newSingerThreadExecutor

所谓的单例线程池,也不过是fixed混合线程池的默认最大都只给了1。这里没必要多做解释了

 

 

 

 

 

 

 

 

 

 

 

5、newScheduledThreadPool

延迟线程池,不管是哪种工厂,与之前三种不同的是,其都是创建ScheduledThreadPoolExecutor对象

然而他们本质上没有什么区别,因为他是继承自ThreadPoolExecutor的

 

 

5.1、RunnableScheduledFuture

 

 

        5.1.1、来源

这个类是newScheduledThreadPool延迟线程池,中工作队列的载体,也即是下图这样得关系

 

 

 

        5.1.2、类关系

首先他本是只是一个接口,继承自RunnableFuture和ScheduledFuture得一个接口,那么这俩又是个什么玩意呢?请接着往下看

首先是RunnableFuture如图,继承自Runnable和Future,回想一下FutureTask中,提到的Executors中一个Runnable和Callable得适配转换器,这里一样,RunnableFuture只是个适配器接口,提供Runnable和Future得相互转化

然后是ScheduledFuture,继承自Delayed和Future,也可知同样得套路,也是一个Delayed和Future得适配转换器

结论:推论出,RunnableScheduledFuture实际上同样是一个适配转换器,从名字就能记住,他提供Runnable、Delayed、Future之间得互相转换

 

 

 

 

5.2、DelayedWorkQueue

 

 

 

 

        5.2.1、来源

他是newScheduledThreadPool延迟线程池中,工作队列的一个选择

 

 

 

        5.2.2、类关系

从名字可以看出,他属于队列集合中的一种,同时是一种只能传入Runnable的阻塞队列

继承自抽象类,抽象队列

再继承自抽象集合,实现了集合接口

 

 

 

 

 

        5.2.3、构造方法

他没有构造方法

他的所有参数的初始化并非在构造方法内实现的,是直接给的

 

 

 

 

 

        5.2.4、offer方法

这里,不管是put还是add,都是调用offer方法,而offer入队方法很简单,出队入队加锁,锁是同一把锁,加锁入队操作,容量不足扩容,由上自下顺序看方法,grow扩容

关于扩容如此,扩容50%

然后setIndex,在每个ScheduledFutureTask中的头节点参数指定,而ScheduledFutureTask实际上是实现上文提及的RunnableScheduledFuture的并且继承了FutureTask,不用说,ScheduledFutureTask类就是在Runnable,Layed,Future的互相转换的基础上,去增加了Task的互相转换

然后是siteup,下图源码第一句,parent = (k - 1) >>> 1,这一句很熟悉啊,脑袋里面反应,堆排序,数组形式,所以刚刚提及的heapIndex就不难理解了,堆中索引,一个逻辑值,跟数组索引没有实际关系,所以总结一下,offer方法插入,实际就是一个加锁后堆排序插入罢了

 

 

 

 

        5.2.4、take方法

如图,先不考虑其在线程池的应用,他既然是队列,就该以标准的生产者消费者模型去看待,首先加锁,队列为空,无从消费,那就阻塞,上文的offer方法也看到了,当队列插入的元素为第一个时,激活所有的阻塞线程,这里,会很纳闷,那么阻塞之后再激活,岂不是拿不到元素了?注意,这里for(),是自旋,自旋消费。剩下没什么好解读的了,就是延时时,使用阻塞

 

 

 

 

 

 

 

 

 

5.2、ScheduleThreadPoolExecutor

 

 

        5.1.1、构造器

但是还是有些区别,再来从构造器开始看起,他对应于ThreadPoolExecutor,不一样的是,Max大小为固定Interger.MAX_VALUE,然后他的工作队列用的是DelayedWorkQueue,DelayedWorkQueue是一个BlockQueue,他同时也是ScheduleThreadPoolExecutor的一个内部类

 

 

 

 

        5.1.2、executor方法

上文已经了解了ScheduleThreadPoolExecutor的隶属,以及它e的构造器,接下来看看他的关键方法execute

主要实现再schedule中,校验之类的跳过,主要关注decorateTask方法和delayedExecute方法,还有TriggerTime方法,首先使用ScheduleFutureTask这个转换类承载Runnable command,然后执行delayedExecute,

首先是decorateTask方法,好像很多余一个参数

然后是triggerTime方法,返回DelayedWorkQueue工作队列的根节点,上文提及了,他是堆排序的,  但这里跟他结构无关,主要是返回一个TimeUtil标准,delay大于long最大值时的一个特殊处理

最后是delayedExecute,这个类比较复杂,看似很短,首先关闭状态时抛出异常,就不用解释了。然后是,向延迟队列中添加任务,没有运行的话就移除,并且取消,在添加后无异常的话,来到ensurePrestart方法,初看很纳闷,向worker工作线程中,加入Null任务,咋一看不太明白是什么意思,但是想想就明白了,因为延迟线程池,实际上是继承自ThreadPoolExecutor的,所有的core大小,max大小都在父类中,addWorker空转,只是简单的看看此时的线程池中的delayed工作队列中的线程数量有没有超过core大小,有没有超过max大小,但实际上,这些都没啥用,ensurePrestart本身就做了这种判断了,请看下面的第二个图和第三个图,使用addWorker的唯一原因,只是为了更新神秘算法的状态值ctl。

所以他并不使用实际意义上的工作线程,Worker,要非要说他的工作线程的话,只能是delayedworkQueue中的,提供了Future、Runnable、Task、Delayed的语义转换的ScheduledFutureTask

 

 

 

 

 

 

 

 

 

 

 

 

 

6、Worker

Worker,就是指工作线程,我把它单独分成一个大节并非是说明他有多复杂,而是因为它的重要程度很高。也是贯穿线程池中的核心对象

 

 

6.1、来源

他是ThreadPoolExecutor的内部类,这样的结构来源,并不能说明它的重要性,这么说吧。

他是很多书本或是博客上提及的Executor框架中线程池的工作线程, 任务提交到工作线程指的就是他,也是大多数线程池的"任务载体",工作队列中承载的就是它。说了一堆有的没的 ,那么看看这玩意实际来源 。如果上面看的认真的话,这部分就可以跳过了

addWorker方法中,方法名就已经很直截了当了,添加Worker,随后是new 了一个Worker在满足条件的情况下添加

 

 

 

 

 

6.2、类关系

满足AQS的线程

 

 

 

 

 

 

 

 

 

 

 

6.3、构造器

看到firstTask,我的第一反应是,他会承载很多任务,自动调度执行,但是看了变量

变量官方说明,thread this worker is running in,Initial task to run,看了说明无法彻底明白他们的用途,这里暂且放下,有些变量是在使用中向用户展现它的价值的

 

 

 

 

 

 

6.4、run方法

它既然本质上是个线程,那么run一定是它的核心方法,这点毫无疑问。

但是这里只用了runWorker。我们通常所说的调度交给了线程池管理,其实这句就能很好的体现了

 

 

 

 

 

6.5、AQS相关方法

根据经验,AQS一般要override两个方法,一个是请求,一个是释放。

代码也是一目了然,就不赘述了,更改状态,以及独占模式的线程

 

 

 

 

 

 

 

6.5、runWorker方法

其实,这个方法并不在Worker中,他是ThreadPoolExecutor的方法,只不过Worker的run只用了这个方法,我们通过runWorker来探究工作线程的工作原理,才是正道,所以把它纳入Worker下说明,只是这个原因,它能很好的诠释Worker的行为

runWorker比较长,分段截图说明,首先是栈帧内初始化,我们了解到,上文提及的firestTask,是一次性的,得到后清空,但是关于无厘头的解锁,其实下文会接着解释,下文操作中会加锁,提前解锁是为了防止cpu中流水线任务分发带来的滞后问题,firstTask我已经拿到都处理好半天了,你还没置空,下一次操作还拿到未变的firstTask怎么办?简单来说,对于java人而言就是指令重排。

随后会循环取任务,而关于getTask也值得一看

getTask第一段,主要是更新当前ctl状态值的判断,以及更新全局ctl,然后有一段得注意,timed = allowCoreThreadTimeOut || wc > corePoolSize。他是布尔型,这里就决定了是否还等待,因为他是for循环,那么不管是timed还是timedOut都有可能改变

getTask第二段,timed决定了任务是延时入队,还是即时得,上文提到timed和core大小有关,也跟allow参数有关,而allow参数也跟keepalive参数有关,所以得到结论,是否延时入队,是keepalive参数决定得,和core默认大小决定的。由此可知,core默认大小和max最大大小,只是将队列砍成三段,第一段小于core时,即时take插入,第二段core和max之间的,poll延时插入,第三段,超过max的,不插入

看完getTask再总结和executor的方法的区别,即是,如同方法名一样。executor是在提交任务时候,对队列的操作,而runWorker是在运行中,对队列的操作。

然后回来接着看runWorker第二段,循环的第一段,根据ctl最近的位运算状态,判断是不是该中断了。

runWorker最后一段,beforeExecute见名知意,我们终于要开始正文了,执行前处理before内容,就如同awk命令,处理这一行前处理begin,处理完后处理end一样。之后,说实话,这一段代码写的很漂亮,个人很喜欢,直接运行task.run捕获异常抛出异常,之后如同beforeExecute一样,afterExceute,执行完后的动作。最终解锁,也看到了Worker中completedTasks是用于记录自己完成了多少任务的。

然后Work退出,processWorkerExit方法也看一下把

processWorkerExit方法,前面更新ctl就不说了。加锁。completedTaskCount是什么呢?相对于worker自身完成任务的计数,也要统计所有的Worker完成任务的计数。随后移除工作队列,解锁,完成。更新ctl什么的就不介绍了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值