关于System.nanoTime()的一点理解

关于System.nanoTime()的一点理解事情起源最近在研究Java中的Random实现的时候,发现Random的种子有用到System.nanoTime()publicRandom(){  this(seedUniquifier() ^ System.nanoTime());}以前在学Java的时候,就听别人说过,Java中的随机数都是伪随机数,是通过随机种子通过复杂的计算得到的,而Java中的种子和时间有关,...

事情起源

最近在研究Java中的Random实现的时候,发现Random的种子有用到System.nanoTime()

public Random() {
    this(seedUniquifier() ^ System.nanoTime());
}
只听到从架构师办公室传来架构君的声音:
此情谁共说。有谁来对上联或下联?

以前在学Java的时候,就听别人说过,Java中的随机数都是伪随机数,是通过随机种子通过复杂的计算得到的,而Java中的种子和时间有关,但是为什么用的是这个方法呢?搜索一番之后得出结论,原来是因为,现在计算机的运行速度很快,如果使用常规时间去做种子的话,有可能出现生成的随机数差别很小甚至完全一样的情况。而nanotime()这个方法,返回的是纳秒级精度的时间,以此作为种子更能保证生成数字的随机性。

出于好(无)奇(聊),我便研究了下这个方法。

首先是源码

在IDE中点进方法源码,发现这个方法是个调用系统实现的native方法,具体实现源码是看不到的

此代码由Java架构师必看网-架构君整理
public static native long nanoTime();

没了源码只能看看文档了,文档中对这个方法的介绍如下(文档来自javadoc,通过谷歌翻译为中文)

返回正在运行的Java虚拟机的高分辨率时间源的当前值,以纳秒为单位。

此方法只能用于测量经过的时间,并且与系统或挂钟时间的任何其他概念无关。返回的值表示纳秒,因为某些固定的任意原点时间 (可能在将来,因此值可能为负)。

在Java虚拟机的实例中,所有对此方法的调用都使用相同的原点;其他虚拟机实例可能使用不同的来源。 此方法提供纳秒级精度,但不一定是纳秒级分辨率(即,值的变化频率) - 除了分辨率至少与currentTimeMillis()的分辨率一样好之外,不做任何保证。

连续调用的差异超过大约292年(2^63纳秒)将无法正确计算由于数值溢出而导致的经过时间。 只有在计算在Java虚拟机的同一实例中获得的两个此类值之间的差异时,此方法返回的值才有意义。

例如,要测量某些代码执行所需的时间:

long startTime = System.nanoTime(); // ...正在测量的代码...... long estimatedTime = System.nanoTime() -  startTime; 比较两个nanoTime值 long t0 = System.nanoTime(); ... long t1 = System.nanoTime();

一个应该使用t1 - t0 <0,而不是t1 <t0,因为数字溢出的可能性.

Returns:正在运行的Java虚拟机的高分辨率时间源的当前值,以纳秒为单位

从以下版本开始:1.5

嗯...翻译有点感人,不过大概能看懂是干嘛的。

返回正在运行的Java虚拟机的高分辨率时间源的当前值,以纳秒为单位。
此方法只能用于测量经过的时间,并且与系统或挂钟时间的任何其他概念无关。

System.nanoTime()是基于cpu核心的时钟周期来计时,它的开始时间是不确定的,网上有篇文章说是更加cpu核心的启动时间开始计算的,不过具体的我也不能确定,只知道是与cpu有关就是了。

从文档来看,System.nanoTime()这个方法一个比较显著的应用是用来提供高精度的计时,不过两次调用的间隔不能超过2^63纳秒(大概292年),目前来看,应该暂时没有人有这么长的需求...

我在网上看到有人说,使用System.nanoTime()去计时会有隐患,比如在多核处理器上运行,不同的调用可能获取的是不同的核心的时间,而多核处理器不同核心的启动时间可能不完全一致,这样会造成计时错误,但是在Stack Overflow上我找到了一个问答:

Is System.nanoTime() completely useless?  

这个问答里面说明,在windows系统上这曾经是一个问题,但是现在它被修复了,因此现在使用System.nanoTime()很安全(至少在同一个虚拟机内)。

还有一个值得关注的问题是,System.nanoTime()的性能不如System.currentTimeMillis(),这是因为

System.currentTimeMillis()是使用GetSystemTimeAsFileTime方法实现的,该方法基本上只读取Windows维护的低分辨率时间值。根据所报告的信息,读取这个全局变量自然很快 - 大约6个周期。 System.nanoTime()使用实现所述QueryPerformanceCounter/ QueryPerformanceFrequency API(如果可用的话,否则它返回currentTimeMillis*10^6)。 QueryPerformanceCounter(QPC)在这取决于它的运行在硬件上不同的方式实现。通常,其使用两可编程间隔计时器(PIT)或ACPI电源管理计时器(PMT),或CPU级别的时间戳计数器(TSC)访问PIT / PMT需要执行慢速I / O端口指令,因此QPC的执行时间大约为几微秒。 100个时钟周期的顺序(从芯片读取TSC并将其转换为基于工作频率的时间值)。

但是即使性能较慢,System.nanoTime()仍然能够提供更精确的计时,因为自身的精度足够高,即使单次运行需要耗费额外的几纳秒(也许),也无足轻重。

总结

总的来说,这个方法主要的应用还是集中在高精度计时上,当然,拿来做随机种子也是一个重要应用,但是Random类我们不需要去实现。

说到Random,好像我原来要研究的是Random来着,算了,不管了,下次有空再说吧,这篇文章就到这里结束了,感谢阅读(然而好像并没有什么干货)。

架构君码字不易,如需转载,请注明出处:https://javajgs.com/archives/209354
0

发表评论