ScheduledExecutorService中scheduleAtFixedRate方法与scheduleWithFixedDelay方法的区别
ScheduledExecutorService中scheduleAtFixedRate方法与scheduleWithFixedDelay方法的区别
- ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,可以作为线程池来使用,同时实现了ScheduledExecutorService接口,来执行一些周期性的任务。ScheduledExecutorService一般常用的方法主要就4个 - public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
 public <V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit);
 public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit);
 public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay, long delay,TimeUnit unit);
 - 两个schedule方法都很明确,就是执行一次Runnable、Callable任务。 - scheduleAtFixedRate和scheduleWithFixedDelay这两个方法看起来就不是那么好区分了,今天就带大家从源码角度看看这两个方法的区别. 
- 我们先看看这两个方法的区别 - 下面是这两个方法的源码   
 从上面的图上可以看到唯一的不同就是在创建ScheduledFutureTask对象的时候,scheduleWithFixedDelay将我们传入的delay取了个负数。所以这两个方法的区别都会在ScheduledFutureTask这个类中。
 先说下ScheduledFutureTask这个类吧,它是ScheduledThreadPoolExecutor的内部类,我们看下它的继承关系

从上图中能看到ScheduledFutureTask,间接继承了Runnable接口,会实现run方法。而我们ScheduledThreadPoolExecutor类中真正执行任务的类其实也就是调用ScheduledFutureTask的run方法。也间接实现了Comparable接口的比较方法。
- 下面以scheduleAtFixedRate看看内部调用逻辑 - public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
 long initialDelay,
 long period,
 TimeUnit unit) {
 if (command == null || unit == null)
 throw new NullPointerException();
 if (period <= 0L)
 throw new IllegalArgumentException();
 ScheduledFutureTask<Void> sft =
 new ScheduledFutureTask<Void>(command,
 null,
 //这里是计算首次执行实现
 triggerTime(initialDelay, unit),
 unit.toNanos(period),
 sequencer.getAndIncrement());
 //这里当前是直接返回的上面的sft,这个是留给子类去扩展的
 RunnableScheduledFuture<Void> t = decorateTask(command, sft);
 sft.outerTask = t;
 //所以这里也就是把上面创建的ScheduledFutureTask加到线程池的任务队列中去
 delayedExecute(t);
 return t;
 }
 - 这里是ScheduledFutureTask的构造方法 - ScheduledFutureTask(Runnable r, V result, long triggerTime,
 long period, long sequenceNumber) {
 super(r, result);
 //这个是任务首次执行的时间
 this.time = triggerTime;
 //这里的代码很简单,只是将它赋值给了成员变量period。
 //其中scheduleWithFixedDelay是在外面取了个负数传了进来,scheduleAtFixedRate则是原样传了进来
 this.period = period;
 //这个是AtomicLong类型的,每次都+1,对我们加入的任务做了个编号
 this.sequenceNumber = sequenceNumber;
 }
 - 我们再看看 - delayedExecute(t);的内部- private void delayedExecute(RunnableScheduledFuture<?> task) {
 if (isShutdown())
 reject(task);
 else {
 //重点在这里,这里会把上面的ScheduledFutureTask加入到线程池的任务队列中,
 //这里的super.getQueue()这个队列,是在ScheduledThreadPoolExecutor构造方法中定义的,也是ScheduledThreadPoolExecutord的内部类,类名是DelayedWorkQueue
 //DelayedWorkQueue其实是一个最小堆,会对加入它的元素,调用compareTo方法进行排序,首个元素是最小的
 //对应当前这里,就是调用ScheduledFutureTask的compareTo方法进行排序,也就是队列中的任务是按照执行时间的先后顺序排序的
 //最终线程池执行任务的时候从首部依次获取task,具体获取任务的时候,DelayedWorkQueue会首先获取任务,查看对应的执行时间,如果任务时间没有到,就会调用Condition.awaitNanos去暂停,直到到达执行时间或者通过给队列中添加任务调用Condition.signal去唤醒
 super.getQueue().add(task);
 if (!canRunInCurrentRunState(task) && remove(task))
 task.cancel(false);
 else
 //这里是根据线程池当前的线程数,如果小于核心线程数,就会新启动线程去执行任务
 ensurePrestart();
 }
 }- 上面已经可以看到把任务已经加到线程池中去了,后面就是具体由线程池去执行任务了,所以我们直接去ScheduledFutureTask查看run方法就可以了 - public void run() {
 if (!canRunInCurrentRunState(this))
 cancel(false);
 else if (!isPeriodic())
 super.run();
 //具体在这里会调用我们传入的run方法
 else if (super.runAndReset()) {
 //在这里会更新成员变量time,scheduleAtFixedRate和scheduleWithFixedDelay的区别也全在这里了,下面我们去看看这里
 setNextRunTime();
 //这里会重新将outerTask加入到线程池的任务队列中,这里的outerTask==我们当前执行run方法的对象this
 reExecutePeriodic(outerTask);
 }
 }
 }
 - 通过上面的代码也能看到,我们的run方法是不会同时由多次执行的,举个例子,如果我们调用scheduleAtFixedRate或者scheduleWithFixedDelay方法,传入的Runnable的对象,需要执行10s,而我们设定的周期是2s,是不会在第一次Runnable的10s的周期任务启动后2s,就启动第2次周期任务的。它只会在第一个Runnable的10s的周期任务结束后,重新加入到任务队列中之后,才会启动下次的任务。 - private void setNextRunTime() {
 //这里的period ,scheduleAtFixedRate传入的是正数,scheduleWithFixedDelay传入的是负数
 long p = period;
 if (p > 0)
 //所以scheduleAtFixedRate会走这里,这里的time开始时时首次任务的开始执行时间,所以下次任务的时间就是(开始添加任务时计算出来的首次任务执行时间(这个时间不一定是任务首次执行的真正时间)+(任务执行次数-1)*period)
 time += p;
 else
 //这里对p取负,就会还原成正数,也就是我们最初调用scheduleWithFixedDelay时传入的值,这里的下次执行时间会用当前系统时间(可以看成当前Runnable执行的结束时间)+period来设置
 time = triggerTime(-p);
 }
 
- 结论 - scheduleAtFixedRate或者scheduleWithFixedDelay对于从第2次开始的任务的计算时间不一样: - scheduleAtFixedRate 下次任务的时间=(开始添加任务时计算出来的首次任务执行时间+(任务执行次数-1)*period
- scheduleWithFixedDelay 下次任务的时间=当前任务结束时间+period
 - 需要注意的是,下次任务时间都只是计算出来的理论值,如果任务的执行时间大于周期任务的period,或者设置的线程池中线程太少,就会出现下次任务执行时间<时间任务执行时间 
ScheduledExecutorService中scheduleAtFixedRate方法与scheduleWithFixedDelay方法的区别的更多相关文章
- 理解ScheduledExecutorService中scheduleAtFixedRate和scheduleWithFixedDelay的区别
		scheduleAtFixedRate 每间隔一段时间执行,分为两种情况: 当前任务执行时间小于间隔时间,每次到点即执行: /** * 任务执行时间(8s)小于间隔时间(10s) */ public ... 
- 关于scheduleAtFixedRate方法与scheduleWithFixedDelay的使用
		一.scheduleAtFixedRate方法 该方法是ScheduledExecutorService中的方法,用来实现周期性执行给定的任务,public ScheduledFuture<?& ... 
- 聊Java中的任务调度的实现方法及比较
		前言 任务调度是指基于给定时间点,给定时间间隔或者给定执行次数自动执行任务.本文由浅入深介绍四种任务调度的 Java 实现: Timer ScheduledExecutor 开源工具包 Quartz ... 
- 【转】Android开发中让你省时省力的方法、类、接口
		转载 http://www.toutiao.com/i6362292864885457410/?tt_from=mobile_qq&utm_campaign=client_share& ... 
- J2EE项目开发中常用到的公共方法
		在项目IDCM中涉及到多种工单,包括有:服务器|网络设备上下架工单.服务器|网络设备重启工单.服务器光纤网线更换工单.网络设备撤线布线工单.服务器|网络设备替换工单.服务器|网络设备RMA工单.通用原 ... 
- (转)ORA-12514 TNS 监听程序当前无法识别连接描述符中请求服务 的解决方法
		早上同事用PL/SQL连接虚拟机中的Oracle数据库,发现又报了"ORA-12514 TNS 监听程序当前无法识别连接描述符中请求服务"错误,帮其解决后,发现很多人遇到过这样的问 ... 
- 解析Jquery取得iframe中元素的几种方法
		iframe在复合文档中经常用到,利用jquery操作iframe可以大幅提高效率,这里收集一些基本操作,需要的朋友可以参考下 DOM方法:父窗口操作IFRAME:window.frames[&q ... 
- 在html中添加script脚本的方法和注意事项
		在html中添加script脚本有两种方法,直接将javascript代码添加到html中与添加外部js文件,这两种方法都比较常用,大家可以根据自己需要自由选择 在html中添加<script& ... 
- MVC中使用Entity Framework 基于方法的查询学习笔记 (二)
		解释,不解释: 紧接上文,我们在Visual Studio2012中看到系统为我们自动创建的视图(View)文件Index.cshtml中,开头有如下这句话: @model IEnumerable&l ... 
随机推荐
- Matplotlib不能显示中文和正负号的问题
			参考链接:https://www.jianshu.com/p/240ea3ae0dc9 在使用matplotlib画饼状图时,遇到了如下问题 UserWarning: findfont: Font f ... 
- sql  select 1 和  exists
			SELECT * FROM LACOMMISION WHERE MANAGECOM LIKE'8694%' AND STATE='1' AND EXISTS(SELECT 1 FROM laagent ... 
- QML用同一模版多开主窗口
			如何动态地创建多个长的一样的主窗口哪(数据当然不一样), 用QML也是可以实现的. 简单的地说, 就是调用多次load即可. QCoreApplication::setAttribute(Qt::AA ... 
- IDEA输出乱码“淇℃伅”
			尝试过在IDEA中tomcat的vmoptions中加入代码 -Dfile.encoding=GBK 没什么用 最后找到了这个方法 参考链接 ①在Tomcat文件夹下找到conf ②找到logging ... 
- Calcite(一):javacc语法框架及使用
			是一个动态数据管理框架. 它包含许多组成典型数据库管理系统的部分,但省略了存储原语.它提供了行业标准的SQL解析器和验证器,具有可插入规则和成本函数的可自定义优化器,逻辑和物理代数运算符,从SQL到代 ... 
- 洛谷P3067题解
			题面 首先,对于每个数,有三种状态:选入集合A,选入集合B,或者不选入集合.暴力枚举的时间复杂度是 \(O(n\times3^n)\) ,显然跑不过去. 因此考虑 \(\text{Meet in Mi ... 
- .net 知新:【4】NuGet简介和使用
			在包管理以前我们在项目中引用第三方包通常是去下载dll放到项目中再引用,后来逐渐发展成各种包管理工具,nuget就是一种工具,适用于任何现代开发平台的基本工具可充当一种机制,通过这种机制,开发人员可以 ... 
- 95后新同事年薪35W+,老员工却“自愿申请”降薪10%,中年职场人正在崩溃
			蔡依林在演唱会上说过的一句话:"在乐坛摸爬滚打这么多年,遭遇了那么多质疑和嘲讽还能挺立到今天,然后想说40岁是个很棒的年纪......",让很多在职场打拼多年的老员工感慨颇深. 真 ... 
- Android工程师所必经的三个阶段,你到哪个阶段了?
			前言 最近一直在思考,作为一名软件开发工程师,到底应该如何实现自我成长,是否有捷径而言?其实断断续续有过很多思考,也有和各种年龄段的同学们做过不少交流,结合自身的经历,有一些感悟和思考.本文可能可以适 ... 
- HDFS中NameNode工作机制
			引言 NameNode: 存储元数据 管理整个HDFS集群 DataNode: 存储数据的block SecondaryNameNode: 辅助HDFS完成一些事情 NameNode和Secondar ... 
