系统当前时间(System.currentTimeMillis)的返回值是否总是大于等于之前调用的值?

53

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

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

我不确定是否保证该代码始终会输出递增的(或相同的)数字。

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

请考虑引用更新的链接。 - trashgod
5
FYI - 正确提出这个问题的方式是询问 currentTimeMillis 是否单调 - R Samuel Klatchko
3
从技术上讲,它是单调递增的。单调函数可以是递增或递减的。 - Sean Reilly
5个回答

66
简短的回答是不,System.currentTimeMillis()不是单调的。它基于系统时间,因此在时钟调整(例如通过NTP)的情况下,可能会出现正向或反向的变化。
只有在底层平台支持CLOCK_MONOTONIC时,System.nanoTime()才是单调的。关于这个问题,可以参考Java bug report 6458294上的评论,其中对一些情况进行了很好的说明。
此外,作为一个额外的趣闻,我个人观察到(多次)在没有时钟调整的情况下,跨线程运行的System.currentTimeMillis()会“倒退”——也就是说,在一个线程中调用该方法返回的值比另一个线程中调用它的值要低,尽管它在“实时”上发生的时间上是晚于它。
如果你需要一个单调的来源,支持单调性的平台上的System.nanoTime()是你最好的选择。

+1 有启发性。您能详细说明一下您如何“亲自观察” currentTimeMillis() 倒流的情况吗?这似乎是一个非常棘手的问题,想到了海森堡原理。您是否以某种方式同步了线程?即使如此,您仍然可以观察到时间倒流吗? - Evgeniy Berezovsky
4
简单版本是,我们有一些定时任务,其中存储了 long start = System.currentTimeMillis() ,然后启动另一个线程来完成工作,该线程本身在结束时执行了 long end = System.currentTimeMillis() 并进行了相减。在非常短暂的任务中,这种差异通常是负数,这让我感到非常困惑,直到我进行了一些研究并挖掘了代码。 :) - Cowan
那么线程#1执行了long startInCallingThread = Sys.cTM()并将该值传递给线程#2,而对于Sys.cTM() - startInCallingThread,线程#2会得到负值?有趣。你可能是在虚拟机中运行它吗?而你所说的缺少时钟调整,是指仅手动调整还是也包括自动调整,例如由ntp守护程序引起的调整? - Evgeniy Berezovsky
1
是的,确实是这样的。这是在真实的硬件上运行的,没有进行NTP调整(我们已经检查过了)。我现在实际上无法回忆起来这是在Windows还是Linux上 - 我们在其中一个上开发,另一个上部署。从记忆中,我们的Linux系统上的currentTimeMillis是不单调的,所以我们切换到了currentTimeNanos(这是最近推出的),只是被上述引用的错误所困扰,使得它在Windows上也变得不单调了。时间很难 :) - Cowan
不应使用System.nanoTime()来获取纳秒级的墙上时间,因为它不能保证是非负的。文档说明返回的值应仅用于减法表达式中,以确定调用之间经过的时间。 - John Calcote

13

不,它并非始终大于等于之前的所有调用。

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

  • 如果您从多个线程中快速连续调用它多次,则它可能做任何事情--根据实现和随机机会,它可能会在时间上轻微地向后跨越线程。

  • 最严重的是,如果用户(很少)或NTP同步(可能常见)调整了系统时钟,则值可能会向后大幅度调整。


9

基于用户在调用之间可能更改系统时间的事实,无法保证其一定会增加。

除此之外,它应该保持增加,因为它表示自历元以来的毫秒数。如果它是普通的“墙上时间”,你就需要担心闰年或夏令时变更时的时间变化。


1
事实上,它保证是周期性的。大约每292,277,020年,它会打印一个极大的数字,然后是一个极小的数字。 - emory

7
如果您想要一个单调递增的值,可以像这样做。
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
1
@MrMesees,你也可以这样编写微秒级定时器,并使用AtomicLong来避免使用synchronized锁。 - Peter Lawrey
请明确一下,原子长整型是否将同步/锁定移动到长整型实例的setter实现中?也就是说,它是将家具搬迁,但封装起来;还是有更好的理由使用它? - MrMesees
1
迟做总比不做好:@MrMesees - 没有理由不喜欢这种方法 - 这正是Linux MONOTONIC_CLOCK的工作方式。 - John Calcote

1

马克·拉沙科夫是对的; nanoTime()可能会更可靠一些。

补充说明:请注意这些警示, 引用自@Steven Schlansker。


2
请注意以下内容: https://dev59.com/anRB5IYBdhLWcg3wyqKo - Steven Schlansker
1
System.nanoTime不能保证是非负的。文档说明你应该只在表达式中使用此方法的值,其中较早的值从较晚的值中减去以确定经过的时间。 - John Calcote

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接