浅谈Executor逻辑架构和运行原理

引言

内容主要介绍,这个框架的构成及运行逻辑,再细化到每一个分部的运行逻辑,不涉及代码和源码,个人觉得,介绍底层逻辑的时候,贴代码和源码来解释说明的都是对于初学者都是耍流氓。把这个东西的逻辑浅显易懂的讲明白后,别人看起源码理解起来轻松许多的同时,还各有各的感悟,这才是分析学习的良好方式。

Executor架构

简单来理解就是一个任务调度器,其架构分为三个部分。
一、任务的产生
所谓任务,就是一个实现了,Runnable接口或者Callable接口的类的对象。这两者的区别在于实现Runnable接口没有返回值,Callable接口有返回值。为什么是这两个接口,想必不用解释了吧,需要解释的,建议先去学习学习线程的知识,然后再学习并发的内容和框架。
二、任务的执行
有任务的产生,必然有任务的执行了,老祖接口Executor,只有一个execute方法,主要用的是Executor接口的子接口ExecutorService,扩展了很多方法,接口的方法终究还得靠实现类去实现,Executor架构的核心实现类为ThreadPoolExecutor,这个类的七大参数和四种拒绝策略,在我上一篇博客已经提过了逻辑原理,在此我就不多说了,不过本文会重点介绍一下ThreadPoolExecutor类的子类, ScheduledThreadPoolExecutor类,这个类可以用来周期性的执行任务。
三、异步的结果返回
任务执行完了,怎么也得有个说法,不然谁知道你执行得怎么样了。所以Future接口用来检测任务得返回结果,其常用实现类之一为FutureTask,本文主讲这个类,其余常用类还有ForkJoinTask,RecursiveTask,RecursiveAction等属于Fork join框架的内容了,这里就暂且不谈。
有一说一,确实在此跟个图会蛮有说服力,但作者目前画图能力太差,丑得一匹,就略过了。

ScheduledThreadPoolExecutor类

其实ScheduledThreadPoolExecutor类在继承ThreadPoolExecutor的同时,还有另外一层关系,它实现了ExecutorService接口的一个子接口,关系大户。
回到正题,谈谈它怎么实现周期性的调度任务的。
ScheduledThreadPoolExecutor类在ThreadPoolExecutor类的基础上做的改变如下,首先将他的同步队列改成了一个无界队列DelayQueue,这样一来最大线程数的设立也就没有必要了,所以其只要关注核心线程数的设置就行。DelayQueue队列又在其内部设置了一个优先级队列PriorityQueue
ScheduledThreadPoolExecutor的执行主要分两个部分,
首先添加任务,执行ScheduledThreadPoolExecutor的两个添加方法的任意一个,(这两个方法具体区别不太清楚,不过看源码的话,应该理解起来不难)把任务添加到DelayQueue队列,变成了实现了RnunableScheduledFutur接口的ScheduledFutureTask,简单的理解为添加了一个任务就行。
接下来便是要执行任务了,之所以前面要搞那些添加的操作,主要是为了在每个ScheduledFutureTask任务里面加三个Long型的成员变量,可以理解为一个是执行时间,一个排队号数,一个执行周期。前两个决定了任务的优先级,也就是在优先级队列的地位,执行时间越小,也就是离当前时间越近的,执行优先级越高,当执行时间一样的时候,就看第二个参数,排队号数,也就是先进去的任务,号数越低,优先级越高,基于此两点因素来觉得任务的执行优先级,优先级越高,越先执行。

周期执行任务的逻辑

那么是怎么实现任务周期操作的?ScheduledThreadPoolExecutor的线程池,把优先级高的任务取出来后执行,执行完毕以后,修改time,然后又给放回了队列里面,就这么简单,没想到吧。这就是所谓的周期执行。
这里主要想说的是,从DelayQueue 队列取任务和重新添加执行完的任务到队列的过程。
取过程:执行DelayQueue.take(),简单来说,就是加锁,取任务,解锁。而这个取任务的逻辑如下,加完锁后,去优先级队列找任务,如果是空的,在一个DelayQueue里的一个condition区域等着,每进来一个线程取任务,都得等着任务进来,倘若任务进来,时间参数大于当前时间,还是得进行等着,直到时间参数等于当前时间了,现在已经有不少线程在condition里面等着了,而且哪怕条件达到了,也都得继续等着。直到有新得线程进来以后,这时恰好优先级队列里面有任务,不为空了,则立即唤醒所有在condition等待的线程,获取任务。然后解锁,完成整个过程。
添加过程:大同小异的加锁解锁,可聊的也是添加任务的逻辑,这里就比较简单了,如果添加的任务是PriorityQueue的头元素,那么直接唤醒所有在condition区等待的线程。

FutureTask类

FutureTask不仅继承了Future接口,还继承了Runnable接口,所以可以直接用Executor来运行。也可以调一个线程来执行**FutureTask.run()**方法。
FutureTask.run()方法一共有三种运行状态,尚未开始,正在运行,和已经结束。已经结束又含三种情况,也就是,运行过程被取消而结束,运行过程被中断停止而结束,和正常完成结束。
FutureTask.cancel(),在尚未开始状态之前调用,那么任务永远不会被执行,在运行期间调用,得设置为FutureTask.cancel(true),线程就会尝试去中断取消任务,设置为false的话则没有任何效果,在运行结束时调用的话,则无论设置为什么参数都将返回false。

FutureTask.get(),在尚未开始,正在运行两种状态的时候都将被阻塞,知道到达已经结束状态的时候,立即返回结果或者抛出异常。
以上方法的说明就很容易理解,这些方法的底层逻辑,肯定都是要去判断状态的这个标志位,来完成一系列的逻辑操作。所以FutureTask的底层操作不是去检测这个状态的标志就是去改变这个状态的,从而完成的逻辑。当然方法更底层的细节原理,我就不在这儿说了,因为我想把它放在ForkJoin框架里面一起详解,感觉效果好一些。
也可以通过ExecutorService.submit(…)来运行,它将返回一个FutureTask。然后可以执行get和cancel方法。

结语

Executor的整个框架内容,我就讲诉得差不多了,底层源码其实就是按我描述得逻辑实现得,理解逻辑后,再去看源码,就会发现源码其实就是那么回事,在描述一些简单得逻辑,只不过考虑方方面面细化一些,就显得代码量大了一些,当然还有一些细节和用法我在这里就不一一去说明了,因为基于上述得逻辑运行的分析,你看底层实现源码就会轻松很多,一些扩展和实用就得自己去感悟思考和体会了。这样才是学习的良好方式。如果有什么问题和看法,希望一起探讨,共同学习,一起进步。
QQ:1192375101

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值