【问题标题】:Will System.currentTimeMillis always return a value >= previous calls?System.currentTimeMillis 是否总是返回值 >= 先前的调用?
【发布时间】:2010-06-05 00:37:23
【问题描述】:

https://docs.oracle.com/javase/6/docs/api/java/lang/System.html#currentTimeMillis() 说:

以毫秒为单位返回当前时间。请注意,虽然返回值的时间单位是毫秒,但值的粒度取决于底层操作系统并且可能更大。例如,许多操作系统以几十毫秒为单位测量时间。

我不清楚我是否可以保证这段代码总是打印不断增加(或相同)的数字。

while (1) { 
    System.out.println(System.currentTimeMillis() );
}

【问题讨论】:

  • 请考虑引用更新的链接。
  • 仅供参考 - 问这个问题的准确方法是 currentTimeMillis 是否单调
  • 从技术上讲,它是单调递增的。单调函数可以增加也可以减少。

标签: java linux


【解决方案1】:

简短的回答是否定的,System.currentTimeMillis()不是单调的。它基于系统时间,因此在时钟调整的情况下(例如通过NTP)可能会受到任何一种方式(向前或向后)的影响。

System.nanoTime() 是单调的,当且仅当底层平台支持 CLOCK_MONOTONIC - 请参阅 Java bug report 6458294 上的 cmets,了解在某些情况下这是/不是真的情况。

(另外,我个人观察到(多次)System.currentTimeMillis() 在没有时钟调整的情况下跨线程“向后”运行——也就是说,在一个线程中对该方法的调用返回比另一个线程中的调用低的值,即使它在“实时”中按时间顺序发生)

如果您需要单调源,支持单调的平台上的System.nanoTime() 是您的最佳选择。

【讨论】:

  • +1 信息丰富。您能否详细说明您如何“亲自观察”currentTimeMillis() 倒退?这看起来是一件相当棘手的事情,我想到了海森堡。您是否以某种方式同步了线程?还能观察到时间倒流吗?
  • 简单的版本是我们有一些计时作业隐藏long start = System.currentTimeMillis(),然后启动另一个线程来完成这项工作,它本身在最后完成了long end = System.currentTimeMillis(),并减去了两者。在运行时间非常短的工作的情况下,这种差异(并不少见)是负面的。这让我很困惑,直到我做了一些研究并深入研究了代码。 :)
  • 所以线程#1 执行了long startInCallingThread = Sys.cTM() 并将该值传递给线程#2,这将得到Sys.cTM() - startInCallingThread 的负值?有趣的。您是否可能在虚拟机中运行它? 没有时钟调整,您是指仅手动调整还是包括自动调整,例如由 ntp 守护程序引起的自动调整?
  • 是的,这正是正在发生的事情。这是在真实硬件上运行的,没有 NTP 调整(我们检查过)。我现在实际上不记得这是在 Windows 还是 Linux 上 - 我们在一个上开发并在另一个上部署。从记忆中,currentTimeMillis 在我们的 Linux 机器上是非单调的,所以我们切换到 currentTimeNanos(最近才可用)只是被上面提到的错误所困扰,这使得它在 Windows 上非单调。时间很艰难。 :)
【解决方案2】:

不,它不会总是 >= 所有之前的调用。

  • 如果您从同一个线程快速连续多次调用它,它可能不会每次都增加(我知道这是 >= 的 = 部分,但这种行为经常让人感到惊讶)。

  • 如果您从多个线程快速连续多次调用它,它可能会执行任意数量的操作——它可能会在线程间稍微回退非常小的时间,具体取决于实现和随机机会。

  • 最严重的是,如果用户(罕见)或 NTP 同步(可能很常见)调整系统时钟,则该值可能会在时间上大量回退。

【讨论】:

    【解决方案3】:

    不可能保证增加,因为用户可能会改变调用之间的系统时间。

    除此之外,它应该保持增长,因为它表示自纪元以来的毫秒数。如果这是正常的“挂钟”,您将不得不担心闰日或夏令时转换的时间变化。

    【讨论】:

    • 其实保证是周期性的。大约每 292,277,020 年,它会打印一个非常大的数字,然后是一个非常小的数字。
    【解决方案4】:

    如果你想要一个单调递增的值,你可以这样做。

    public enum Time {
        ;
        private static long lastTime;
        public synchronized static long increasingTimeMillis() {
            long now = System.currentTimeMillis();
            if (now > lastTime)
                return lastTime = now;
            return ++lastTime;
        }
    }
    

    只要您每秒调用它少于一千次,您增加的时间就不会偏离实际时间太远,而是独一无二的。 (即使您重新启动应用程序,这也可以工作)

    【讨论】:

    • 我非常不喜欢这个,但它适用于 GitHub Actions;而 system.nanoTime() 没有
    • @MrMesees 你也可以这样写一个微秒定时器,并使用 AtomicLong 来避免使用synchronized
    • 要明确的是,原子long是否将同步/锁定移动到long实例上的setter实现? AKA 移动家具,但被封装;还是有更好的理由使用它?
    【解决方案5】:

    @Mark Rushakoff 是对的; nanoTime() 可能更可靠一些。

    附录:注意@Steven Schlansker 引用的caveats

    【讨论】:

    猜你喜欢
    • 2013-06-20
    • 2021-06-30
    • 2013-09-19
    • 1970-01-01
    • 2018-11-21
    • 2012-11-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多