如Java博客文章Beware of System.nanoTime()中所述,在x86系统上,Java的System.nanoTime()使用CPU特定计数器返回时间值.现在考虑以下用于测量呼叫时间的情况:
long time1= System.nanoTime(); foo(); long time2 = System.nanoTime(); long timeSpent = time2-time1;
现在在多核系统中,可能是在测量time1之后,线程被调度到不同的处理器,其计数器小于先前CPU的计数器.因此,我们可以得到time2中的值,该值小于 time1.因此,我们将在timeSpent中得到负值.
考虑到这种情况,是不是System.nanotime现在几乎没用?
我知道改变系统时间不会影响纳米时间.这不是我上面描述的问题.问题是每个CPU都会在打开后保留不同的计数器.与第一个CPU相比,第二个CPU上的计数器可以更低.由于在获取time1之后OS可以将线程调度到第二个CPU,因此timeSpent的值可能不正确甚至是负数.
这个答案是在2011年从当时在操作系统上运行的Sun JDK实际所做的事情的角度编写的.那是很久以前的事!leventov的回答提供了一个更新的视角.
那篇文章错了,而且nanoTime
很安全.有关此帖子的评论链接到David Holmes的博客文章,他是Sun的实时和并发人员.它说:
System.nanoTime()使用QueryPerformanceCounter/QueryPerformanceFrequency API实现[...] QPC使用的默认机制由硬件抽象层(HAL)决定[...]此默认设置不仅在硬件之间更改,而且在操作系统之间更改版本.例如,Windows XP Service Pack 2改变了使用电源管理计时器(PMTimer)而不是处理器时间戳计数器(TSC)的原因,因为TSC的问题未在SMP系统中的不同处理器上同步,并且由于其频率可以根据电源管理设置变化(因此与经过时间的关系).
因此,在Windows上,这是一个问题,直到WinXP SP2,但它现在不是.
我找不到关于其他平台的第二部分(或更多部分),但该文章确实包含了Linux遇到的注释,并以相同的方式解决了同样的问题,并提供了指向clock_gettime的常见问题解答的链接(CLOCK_REALTIME),其中说:
所有处理器/核心的clock_gettime(CLOCK_REALTIME)是否一致?(拱形是否重要?例如ppc,arm,x86,amd64,sparc).
它应该或它被认为是错误的.
但是,在x86/x86_64上,可能会看到未同步或变量的频率TSC导致时间不一致.2.4内核确实没有针对此的保护,早期的2.6内核也没有做得太好.从2.6.18开始,检测它的逻辑更好,我们通常会回到安全的时钟源.
ppc总是有一个同步的时基,所以这应该不是问题.
因此,如果Holmes的链接可以被理解为暗示nanoTime
调用clock_gettime(CLOCK_REALTIME)
,那么从x86上的内核2.6.18开始就是安全的,并且总是在PowerPC上(因为IBM和Motorola不同于英特尔,实际上知道如何设计微处理器).
遗憾的是,没有提到SPARC或Solaris.当然,我们不知道IBM JVM的功能.但现代Windows和Linux上的Sun JVM正确地做到了这一点.
编辑:这个答案是基于它引用的来源.但我仍然担心它可能实际上是完全错误的.一些更新的信息将非常有价值.我刚刚看到一篇关于Linux时钟的四年新文章的链接,这篇文章可能很有用.
我做了一些搜索,发现如果一个人是迂腐,那么是的,它可能被认为是无用的...在特殊情况......这取决于你的要求对时间的敏感程度......
从Java Sun站点查看此引用:
实时时钟和System.nanoTime()都基于相同的系统调用,因此是相同的时钟.
使用Java RTS,所有基于时间的API(例如,定时器,定期线程,截止时间监视等)都基于高分辨率计时器.并且,与实时优先级一起,他们可以确保在适当的时间执行适当的代码以实现实时约束.相比之下,普通的Java SE API只提供了一些能够处理高分辨率时间的方法,并且无法保证在给定时间执行.在代码中的各个点之间使用System.nanoTime()来执行经过时间的测量应始终准确.
Java还有一个针对nanoTime()方法的警告:
此方法只能用于测量经过的时间,与系统或挂钟时间的任何其他概念无关.返回的值表示纳秒,因为某些固定但是任意时间(可能在将来,因此值可能为负).该方法提供纳秒精度,但不一定是纳秒精度.不保证值的变化频率. 由于数值溢出,跨越大于约292.3年(2 63纳秒)的连续调用的差异将无法准确计算经过的时间.
似乎唯一可以得出的结论是nanoTime()不能作为准确的值来依赖.因此,如果您不需要测量仅相差纳秒的时间,那么即使得到的返回值为负,此方法也足够好.但是,如果您需要更高的精度,他们似乎建议您使用JAVA RTS.
所以回答你的问题......没有nanoTime()没有用处....它不是在每种情况下使用的最谨慎的方法.
无需辩论,只需使用来源.在这里,SE 6 for Linux,得出你自己的结论:
jlong os::javaTimeMillis() { timeval time; int status = gettimeofday(&time, NULL); assert(status != -1, "linux error"); return jlong(time.tv_sec) * 1000 + jlong(time.tv_usec / 1000); } jlong os::javaTimeNanos() { if (Linux::supports_monotonic_clock()) { struct timespec tp; int status = Linux::clock_gettime(CLOCK_MONOTONIC, &tp); assert(status == 0, "gettime error"); jlong result = jlong(tp.tv_sec) * (1000 * 1000 * 1000) + jlong(tp.tv_nsec); return result; } else { timeval time; int status = gettimeofday(&time, NULL); assert(status != -1, "linux error"); jlong usecs = jlong(time.tv_sec) * (1000 * 1000) + jlong(time.tv_usec); return 1000 * usecs; } }
从Java 7开始,System.nanoTime()
通过JDK规范保证安全。 System.nanoTime()
的Javadoc清楚地表明,在JVM中(即在所有线程中)观察到的所有调用都是单调的:
返回的值表示自某个固定但任意的起始时间以来的纳秒(也许是将来的时间,因此值可能为负)。在Java虚拟机的实例中,此方法的所有调用都使用相同的源。其他虚拟机实例可能会使用其他来源。
JVM / JDK实现负责消除调用底层OS实用程序时可能出现的不一致(例如,Tom Anderson的回答中提到的那些不一致)。
该问题的其他大多数旧答案(写于2009–2012年)表示FUD可能与Java 5或Java 6有关,但与现代Java版本不再相关。
值得一提的是,尽管JDK保证了nanoTime()
安全性,但OpenJDK中仍然存在一些错误,使其在某些平台上或某些情况下(例如JDK-8040140,JDK-8184271)不能坚持这种保证。目前,OpenJDK wrt中没有任何公开的(已知的)错误nanoTime()
,但是发现新的此类错误或OpenJDK的较新版本中的回归都不会令任何人感到震惊。
考虑到这一点,代码,使用nanoTime()
对定时阻挡,间隔等待,超时等应优选治疗负的时间差(超时)为零,而不是抛出异常。这种做法也是可取的,因为它是在所有类中的所有定时的等待方法的行为是一致的java.util.concurrent.*
,例如Semaphore.tryAcquire()
,Lock.tryLock()
,BlockingQueue.poll()
等。
但是,nanoTime()
仍应首选实施定时阻塞,间隔等待,超时等,currentTimeMillis()
因为后者会受到“时间倒退”现象的影响(例如由于服务器时间校正),即currentTimeMillis()
不适合测量时间间隔完全没有 有关更多信息,请参见此答案。
代替nanoTime()
直接用于代码执行时间测量,最好应使用专门的基准测试框架和分析器,例如,在壁钟分析模式下的JMH和async-profiler。
免责声明:我是这个图书馆的开发者
您可能更喜欢这样:
http://juliusdavies.ca/nanotime/
但它将DLL或Unix .so(共享对象)文件复制到当前用户的主目录中,以便它可以调用JNI.
一些背景信息在我的网站上:
http://juliusdavies.ca/posix_clocks/clock_realtime_linux_faq.html
Linux纠正了CPU之间的差异,但Windows没有.我建议你假设System.nanoTime()只能精确到1微秒左右.获得更长时间的一种简单方法是调用foo()1000次或更多次并将时间除以1000.
绝对没有用武之地.时间爱好者正确地指出了多核问题,但在实用词应用程序中,它通常比currentTimeMillis()好得多.
在帧刷新中计算图形位置时,nanoTime()可以在程序中实现更平滑的运动.
我只测试多核机器.
我看到使用System.nanoTime()报告了消极的经过时间。要清楚,有问题的代码是:
long startNanos = System.nanoTime(); Object returnValue = joinPoint.proceed(); long elapsedNanos = System.nanoTime() - startNanos;
而变量“ elapsedNanos”具有负值。(我肯定中间调用也花费了不到293年的时间,这是存储在long中的nanos的溢出点:)
这是在运行AIX的IBM P690(多核)硬件上使用IBM v1.5 JRE 64bit发生的。我只看到此错误发生过一次,因此似乎极为罕见。我不知道原因-这是硬件特定的问题,还是JVM缺陷-我不知道。我也不知道通常对nanoTime()的准确性有何影响。
要回答最初的问题,我并不认为nanoTime是无用的-它提供了亚毫秒级的计时,但是有一个实际的(不仅仅是理论上的)风险,您需要考虑它不准确。