System.currentTimeMillis和System.nanoTime


379

精度与 精确

我想知道的是在更新对象在游戏中的位置时应该使用System.currentTimeMillis()还是System.nanoTime()?他们的运动变化与自上次通话以来经过的时间成正比,我想尽可能地精确。

我已经读到不同操作系统之间存在一些严重的时间分辨率问题(即Mac / Linux的分辨率几乎为1毫秒,而Windows的分辨率为50毫秒?)。我主要是在Windows上运行我的应用,而50ms的分辨率似乎非常不准确。

是否有比我列出的两个更好的选择?

有什么建议/意见吗?


81
nanoTime通常比currentTimeMillis准确得多,但这也是一个相对昂贵的电话。currentTimeMillis()运行在几个(5-6)cpu时钟中,nanoTime取决于基础架构,并且可以是100+ cpu时钟。
bestsss 2011年

10
大家都知道Windows通常具有1000ms / 64的时间片粒度,对吗?这是15.625毫秒,即15625000纳秒!

7
我认为没有多余的100个时钟周期会影响您的游戏,因此折衷可能值得。您每次游戏更新仅应调用一次该方法,然后将值保存在mem中,这样就不会增加太多开销。至于不同平台的粒度,我不知道。
aglassman 2012年

9
Windows的默认时间片粒度为1000ms / 64。您可以通过本地的timeBeginPeriod API来增加它。除了基本计时器外,现代PC还具有高分辨率计时器。可通过QueryPerformanceCounter调用访问高分辨率计时器。
罗宾·戴维斯

2
@Gohan-本文详细介绍了以下内容的内部工作System.currentTimeMillis()pzemtsov.github.io/2017/07/23/the-slow-currenttimemillis.html
Attila

Answers:


320

如果您只是在寻找经过时间的极其精确的度量,请使用System.nanoTime()System.currentTimeMillis()将为您提供自该纪元以来最精确的经过时间(以毫秒为单位),但System.nanoTime()相对于任意点,您可以得到十亿分之一秒的精确时间。

从Java文档中:

public static long nanoTime()

返回最精确的可用系统计时器的当前值,以纳秒为单位。

此方法只能用于测量经过的时间,与系统或挂钟时间的任何其他概念无关。返回的值表示自某个固定但任意的起始时间以来的纳秒(也许是将来的时间,因此值可能为负)。此方法提供纳秒精度,但不一定提供纳秒精度。不能保证值更改的频率。 由于数值上溢,跨越大约292年(2 63纳秒)的连续呼叫中的差异将无法准确计算经过的时间。

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

long startTime = System.nanoTime();    
// ... the code being measured ...    
long estimatedTime = System.nanoTime() - startTime;

另请参阅:JavaDoc System.nanoTime()JavaDoc System.currentTimeMillis()了解更多信息。


20
您确定您知道准确度和精确度之间的区别吗?它不可能精确到纳秒级的精度。
mmcdole

6
对不起,我是说精确。我使用的术语比较松散,但我同意它会造成混淆(以及该词的使用不当)。
dancavallaro

4
@dancavallaro,感谢您的信息。如果您不介意,我编辑了您的答案,以包括文档中的引号并修复了链接
mmcdole,

135
在选择nanoTime()时,此答案在技术上是正确的,但在一个非常重要的方面完全没有意义。正如文档所说,nanoTime()是一个精密计时器。currentTimeMillis()不是计时器,它是“挂钟”。nanoTime()总是会产生正的经过时间,currentTimeMillis不会(例如,如果您更改日期,hit秒等),这对于某些类型的系统来说是非常重要的区别。
charstar 2011年

11
用户更改时间和NTP同步肯定可以,但是为什么currentTimeMillis由于DST 而更改?DST开关不会更改经过纪元的秒数​​。它可能是“挂钟”,但它是基于UTC的时钟。您必须根据时区和DST设置确定本地时间的含义(或使用其他Java实用程序为您完成此操作)。
Shadow Man

100

既然没有人提到这个……

比较System.nanoTime()不同线程之间的调用结果并不安全。即使线程事件以可预测的顺序发生,纳秒级的差异也可以是正数或负数。

System.currentTimeMillis() 在线程之间安全使用是安全的。


4
在Windows中,只有持有直到SP2根据:stackoverflow.com/questions/510462/...
彼得·施米茨

3
好吧,您每天都会学到新东西。但是我怀疑,由于过去它是不安全的(肯定会在线程间返回无意义的读数),所以这种用法可能仍超出规范范围,因此应避免使用。
gubby 2012年

4
@jgubby:非常有趣... 对支持的引用,在不同线程之间比较System.nanoTime()调用的结果并不安全 以下链接值得一看:bugs.sun.com/bugdatabase/view_bug.do? bug_id
javase

15
查看此处提到的描述:docs.oracle.com/javase/7/docs/api/java/lang/…,只要它们来自相同的时间,nanoTime返回的值之间的差异似乎可以比较有效JVM -The values returned by this method become meaningful only when the difference between two such values, obtained within the same instance of a Java virtual machine, is computed.
Tuxdude

7
JavaDoc fornanoTime()表示:在Java虚拟机实例中,此方法的所有调用都使用相同的来源。其他虚拟机实例可能会使用其他来源。这意味着它确实跨线程返回相同的结果。
西蒙·佛斯伯格

58

Arkadiy更新:我已经System.currentTimeMillis()在Oracle Java 8中观察到Windows 7上更正确的行为。该时间以1毫秒的精度返回。OpenJDK中的源代码没有更改,因此我不知道是什么导致了更好的行为。


Sun的David Holmes在几年前发布了一篇博客文章,其中非常详细地介绍了Java计时API(尤其是System.currentTimeMillis()System.nanoTime()),何时使用它们以及它们在内部如何工作。

热点虚拟机内部:时钟,计时器和计划事件-第一部分-Windows

Windows上的Java对于具有定时等待参数的API使用的计时器的一个非常有趣的方面是,计时器的分辨率可以根据可能进行的其他API调用而改变-系统范围(不仅限于特定进程) 。他展示了一个使用Thread.sleep()会导致分辨率改变的示例。


1
@Arkadiy:您在更新中是否有声明的来源?
Lii 2015年

@Lii-不幸的是,没有。它来自于我运行此问题中的代码:stackoverflow.com/questions/34090914/…。该代码在Java 7中产生

2
我在OpenJDK的hotspot / src / os / windows / vm / os_windows.cpp(hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/os/…)中将currentTimeMillis跟踪到os :: javaTimeMillis 。看起来它仍然是GetSystemTimeAsFileTime,所以我不知道更改来自何处。或者,即使它是有效的。使用前进行测试。

行为上的变化是GetSystemTimeAsFileTime在XP vs 7 中更改了工作方式的结果 。有关更多详细信息,请参见此处。(tl; dr变得更加精确,因为整个系统引入了一些更精确的计时方法)。

11

System.nanoTime()旧版JVM不支持。如果这是一个问题,请坚持currentTimeMillis

关于准确性,您几乎是正确的。在某些Windows计算机上,currentTimeMillis()分辨率约为10毫秒(而非50毫秒)。我不确定为什么,但是某些Windows机器和Linux机器一样准确。

我过去使用GAGETimer取得了一定的成功。


3
“旧版JVM”与哪个?Java 1.2之类的?
西蒙·佛斯伯格

1
System.nanoTime()于2004年在Java 1.5中引入。Java1.4在2013年扩展了对它的支持,因此可以肯定地说System.nanoTime()现在可以依靠,而且现在的答案已经过时了。
戴夫·L。

9

就像其他人所说的,currentTimeMillis是时钟时间,由于夏时制,用户更改时间设置,leap秒和互联网时间同步而改变。如果您的应用依赖于单调增加的经过时间值,则您可能更喜欢nanoTime。

您可能会认为玩家不会在游戏过程中摆弄时间设置,也许您是对的。但是不要低估由于互联网时间同步或远程桌面用户造成的中断。nanoTime API不受这种破坏的影响。

如果要使用时钟时间,但要避免由于Internet时间同步而造成中断,则可以考虑使用NTP客户端(例如Meinberg),该客户端可以将时钟速率“调整”为零,而不仅仅是定期重置时钟。

我是根据个人经验讲的。在我开发的天气应用程序中,我得到的是随机出现的风速峰值。我花了一段时间才意识到典型的PC上的时钟时间行为干扰了我的时基。当我开始使用nanoTime时,我所有的问题都消失了。一致性(单调性)对我的应用程序比原始精度或绝对精度更重要。


19
“ currentTimeMillis是时钟时间,它会由于夏时制而改变”System.currentTimeMillis()报告自Unix / Posix Epoch(世界标准时间1970年1月1日午夜)以来经过的时间(以毫秒为单位)。因为从不为夏令时调整UTC,所以当当地时区增加时或以夏时制为当地时间设置偏移量时,该值将不会被抵消。此外,Java Time Scale消除了leap秒,因此日期似乎继续具有86,400秒。
斯科特,

5

是的,如果需要使用这种精度System.nanoTime(),但是请注意,您那时需要Java 5+ JVM。

在我的XP系统上,使用以下代码,我看到系统时间至少报告为100微秒 278 纳秒

private void test() {
    System.out.println("currentTimeMillis: "+System.currentTimeMillis());
    System.out.println("nanoTime         : "+System.nanoTime());
    System.out.println();

    testNano(false);                                                            // to sync with currentTimeMillis() timer tick
    for(int xa=0; xa<10; xa++) {
        testNano(true);
        }
    }

private void testNano(boolean shw) {
    long strMS=System.currentTimeMillis();
    long strNS=System.nanoTime();
    long curMS;
    while((curMS=System.currentTimeMillis()) == strMS) {
        if(shw) { System.out.println("Nano: "+(System.nanoTime()-strNS)); }
        }
    if(shw) { System.out.println("Nano: "+(System.nanoTime()-strNS)+", Milli: "+(curMS-strMS)); }
    }

4

对于游戏图形和平滑的位置更新,请使用System.nanoTime()而不是System.currentTimeMillis()。我在游戏中从currentTimeMillis()切换为nanoTime(),在运动流畅度方面有了重大的视觉改善。

尽管一毫秒似乎应该已经很精确了,但在视觉上却并非如此。nanoTime()可以改善的因素包括:

  • 低于挂钟分辨率的精确像素定位
  • 如果需要,可以消除像素之间的锯齿
  • Windows挂钟不正确
  • 时钟抖动(实际时钟向前滴答的时间不一致)

正如其他答案所暗示的那样,nanoTime如果重复调用的确会降低性能,因此,最好每帧只调用一次,并使用相同的值来计算整个帧。


1

我在nanotime方面有很好的经验。使用JNI库,它可以将挂钟时间提供为两个很长的时间(从纪元开始的秒数,在该秒数秒内为纳秒)。它与针对Windows和Linux预先编译的JNI部件一起提供。


1

System.currentTimeMillis()对于经过的时间并不安全,因为此方法对系统的系统实时时钟更改敏感。您应该使用System.nanoTime。请参考Java System帮助:

关于nanoTime方法:

..此方法提供纳秒级精度,但不一定提供纳秒级分辨率(即值更改的频率)-除了分辨率至少与currentTimeMillis()一样好之外,不做任何保证。

如果您使用System.currentTimeMillis()的经过时间可能为负(返回<-返回未来)


-3

这里的一件事是nanoTime方法的不一致。对于相同的输入,它不会给出非常一致的值。currentTimeMillis在性能和一致性方面要好得多,并且尽管不如nanoTime精确,但误差范围较小,因此其值的准确性更高。因此,我建议您使用currentTimeMillis


6
如其他答案和注释中所述,currentTimeMillis会随系统时钟的变化而变化,因此自JVM中发生某个较早的事件以来,计算经过的时间的选择就很差。
Duelin markers 13-10-14
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.