ScheduledThreadPoolExecutor与System#nanoTime
一直流传着Timer使用的是绝对时间,ScheduledThreadPoolExecutor使用的是相对时间,那么ScheduledThreadPoolExecutor是如何实现相对时间的?
先看看ScheduledThreadPoolExecutor中实现定时调度的模型,很简单,内部用了无界的DelayQueue作为线程池的队列,而DelayQueue的内部又使用的是一个PriorityQueue,那么,最先需要定时调度的任务位于队首。定时任务实现逻辑大概如此:创建ScheduledThreadPoolExecutor对象的时候会记录一个常量值t,定时任务中有一个以t为基础的多久以后会被执行的属性,在线程拿到队首任务(可能等待了一段时间)执行后,会修改这个属性为下一次要执行的基于t的时间量,然后将其再放入队列中。整个逻辑都在任务的run方法中:
public if runPeriodic(); else ScheduledFutureTask.super.run();} |
如果是周期性任务,会执行runPeriodic:
private booleansuper.runAndReset(); boolean // if (getContinueExistingPeriodicTasksAfterShutdownPolicy() !isTerminating()))) long if0) time else time ScheduledThreadPoolExecutor.super.getQueue().add(this); } // // else interruptIdleWorkers();} |
下面这段代码就是周期性任务实现的逻辑:
if0) timeelse timeScheduledThreadPoolExecutor.super.getQueue().add(this); |
重新回到相对时间问题,首先看看DelayQueue的take方法:
publicthrows finalthis.lock; lock.lockInterruptibly(); try for E ifnull) available.await(); }else long if0) long }else E assertnull; if0) available.signalAll();// return } } } }finally lock.unlock(); }} |
拿到队列里的第一个元素(也就是最先需要执行的),但并不删除,获取该元素的等待时间(也就是getDelay),然后不断的awaitNanos。如果此时加入了一个比这个队首元素还要先执行的任务会怎样?看下add和offer方法的实现(add方法就是直接调用的offer):
public finalthis.lock; lock.lock(); try E q.offer(o); ifnull0) available.signalAll(); return; }finally lock.unlock(); }} |
变量q即是一个PriorityQueue,如果o对象表示的任务比目前队首的任务更先执行,那么q.offer(o)会将o弄到队首,在这种情况下o.compareTo(first)是小于0的,因此会通知在available上等待的线程。而在take方法里,线程一直在available上awaitNanos,此时若被唤醒,它就会继续循环,重新拿到队列的第一个元素,也就是新加入的元素并重复之前的过程,这样,最先需要调度的任务就永远排在第一位。
回到take方法的long delay = first.getDelay(TimeUnit.NANOSECONDS)这一句,在ScheduledThreadPoolExecutor实现中,这个first变量的类型就是ScheduledThreadPoolExecutor.ScheduledFutureTask,这是ScheduledThreadPoolExecutor中的一个私有类,看看其getDelay方法的实现:
public return } |
其中的now方法:
final return} |
刚开始看到这个代码时,心想这何以实现相对时间。潜意识中对nanoTime认识一直是这样的:返回值是纳秒,与currentTimeMillis()一样返回的是与协调世界时 1970 年 1 月 1 日午夜之间的时间差。经过测试与查看API文档,原来对nanoTime()的认识一直是错误的。
API中关于nanoTime是这么描述的:
返回最准确的可用系统计时器的当前值,以毫微秒为单位。
此方法只能用于测量已过的时间,与系统或钟表时间的其他任何时间概念无关。返回值表示从某一固定但任意的时间算起的毫微秒数(或许从以后算起,所以该值可能为负)。此方法提供毫微秒的精度,但不是必要的毫微秒的准确度。它对于值的更改频率没有作出保证。在取值范围大于约 292 年(263 毫微秒)的连续调用的不同点在于:由于数字溢出,将无法准确计算已过的时间。
返回值的单位为毫微秒不是该方法的重点,重点在于“与系统或钟表时间的其他任何时间概念无关。返回值表示从某一固定但任意的时间算起的毫微秒数”,也就是说,它的返回值是一个相对“某一固定但任意的时间”的偏移量,而不依赖于系统时钟是否改变,也无法通过这个方法的返回值计算当前日期。而这个相对性正是ScheduledThreadPoolExecutor所需要的。
很多计算代码运行耗时的地方使用了currentTimeMillis(),那么在系统时间变动的那一刻(如NTP时间同步),耗时计算结果是不准确的,尤其是时间变动较大时,如果在日志中发现某个调用突然耗时很大,还以为出现什么问题了。
关于nanoTime有些有趣的问题,用该方法计算运行耗时得出的结果竟然会是负数:http://stackoverflow.com/questions/510462/is-system-nanotime-completely-useless,只是看到这个话题,我的系统上(xp
sp3)没有重现。
最后,题外话,关于系统时间:尽可能不要在生产代码中使用Thread#sleep,因为“此操作受到系统计时器和调度程序精度和准确性的影响”。
ScheduledThreadPoolExecutor与System#nanoTime的更多相关文章
- System.nanoTime
System.currentTimeMillis()返回的毫秒,这个毫秒其实就是自1970年1月1日0时起的毫秒数. System.nanoTime()返回的是纳秒,nanoTime而返回的可能是任意 ...
- System.nanoTime与System.currentTimeMillis的理解与区别
System类代表系统,系统级的很多属性和控制方法都放置在该类的内部.该类位于java.lang包. 平时产生随机数时我们经常拿时间做种子,比如用System.currentTimeMillis的结果 ...
- System.nanoTime与System.currentTimeMillis的区别
平时产生随机数时我们经常拿时间做种子,比如用 System.currentTimeMillis的结果,但是在执行一些循环中使用了System.currentTimeMillis,那么每次的结 果将会差 ...
- System.nanoTime()的使用
纳秒 ns(nanosecond):纳秒, 时间单位.一秒的10亿分之一,即等于10的负9次方秒.常用作 内存读写速度的单位. 1纳秒=0.000001 毫秒 1纳秒=0.00000 0001秒 ja ...
- System.nanoTime理解
JDK1.5之后java中的计时给出了更精确的方法:System.nanoTime(),输出的精度是纳秒级别,这个给一些性能测试提供了更准确的参考. 但是这个方法有个需要注意的地方,不能用来计算今天是 ...
- 【填坑纪事】一次用System.nanoTime()填坑System.currentTimeMills()的实例记录
JDK提供了两个方法,System.currentTimeMillis()和System.nanoTime(),这两个方法都可以用来获取表征当前时间的数值.但是如果不仔细辨别这两个方法的差别和联系,在 ...
- System.nanoTime与System.currentTimeMillis的区别(转)
原文地址:http://blog.csdn.net/dliyuedong/article/details/8806868 平时产生随机数时我们经常拿时间做种子,比如用System.currentTim ...
- 对于应用需要记录某个方法耗时的场景,必须使用clock_gettime传入CLOCK_MONOTONIC参数,该参数获得的是自系统开机起单调递增的纳秒级别精度时钟,相比gettimeofday精度提高不少,并且不受NTP等外部服务影响,能准确更准确来统计耗时(java中对应的是System.nanoTime),也就是说所有使用gettimeofday来统计耗时(java中是System.curre
对于应用需要记录某个方法耗时的场景,必须使用clock_gettime传入CLOCK_MONOTONIC参数,该参数获得的是自系统开机起单调递增的纳秒级别精度时钟,相比gettimeofday精度提高 ...
- 我的Java开发学习之旅------>System.nanoTime与System.currentTimeMillis的区别
首先来看一道题:下面代码的输出结果是什么? import java.util.HashMap; import java.util.Map; public class HashMapTest { pub ...
- java: new Date().getTime() 与 System.currentTimeMillis() 与 System.nanoTime()
java使用new Date()和System.currentTimeMillis()获取当前时间戳 在开发过程中,通常很多人都习惯使用new Date()来获取当前时间,使用起来也比较方便,同时 ...
随机推荐
- [WPF]数据绑定时为何会出现StringFormat失效
在数据绑定过程中,我们经常会使用StringFormat对要显示的数据进行格式化,以便获得更为直观的展示效果,但在某些情况下格式化操作并未生效,例如 Button的 Content属性以及ToolTi ...
- airflow 学习
入门 Get started developing workflows with Apache Airflow Getting started with Apache Airflow
- 全面升级,票据识别新纪元:合合信息TextIn多票识别2.0
票据识别 - 自动化业务的守门员 发票.票据识别,是OCR技术和RPA.CMS系统结合的一个典型场景,从覆盖率.覆盖面的角度来说,应该也是结合得最成功的场景之一. 旧瓶装新酒,已经成熟的产品何苦费 ...
- CSS & JS Effect – Virtual Scrolling
前言 我正在写 Angular CDK Scrolling 教程,它里面有一个 Virtual Scrolling 功能.借此机会,我想顺便写一篇纯 Sass & TS 的版本作为学习. Vi ...
- Angular 16+ 高级教程 – Angular 和其它技术方案的比较
前言 上一篇我有提到 Angular 适合用于哪些项目,但讲的太含糊,什么大中小项目的...这篇我将更具体的去讲解,Angular 的定位,还有它和其它方案的优缺点. Web 技术可以用来做许许多多不 ...
- uniCloud
https://doc.dcloud.net.cn/uniCloud/ 什么是uniCloud uniCloud推出了opendb,包含了大量的开源数据库模板,常见数据表无需自己设计 uniCloud ...
- 线段树与二分操作 vases and flowers ——hdu 4614
操作1,的关键是找到第一只和最后一只空花瓶,完全可以利用二分法查找,找第一只花瓶可以在[X,N]内查找,第一个位置pos1,最后一只花瓶则在[POS1,N]中找,然后更新[POS1,POS2],全部置 ...
- Qt表格入门
摘要 表格作为数据展示的界面,会在很多场景下使用.Qt为我们提供了使用简单方便和扩展性强的表格视图,这里做一个简单的入门整理. 个人能力有限,有错误欢迎留言指正,如果你有更好的方法,也 ...
- 生理性喜欢(把我吃掉&病态饥饿)
alloverzyt 转载好文章 https://www.zhihu.com/question/627599874/answer/3494578704 什么是生理性喜欢? 是吃掉. 这不是什么夸张的写 ...
- 图书《React.js实战》代码下载
图书<React.js实战>代码下载链接:https://pan.baidu.com/s/1kep0xsTeSupyr15c3VwmBw 提取码:9pra 这个代码经过图书<Reac ...