Java中是否有任何一致(单调)时钟实现?结论

2022-09-01 07:53:35

默认的 java.time.Clock 实现基于 System.currentTimeMillis()。例如,正如这里所讨论的,在Java中单调地增加时间?,它不能保证是单调的。

事实上,我经常遇到这样一种情况,即系统时间自动调整几秒钟到过去,Java时钟也会跳回去。

//now() returns 2016-01-13T22:34:05.681Z
order.setCreationTime(Instant.now());

//... something happens, order gets cancelled

//now() returns 2016-01-13T22:34:03.123Z
//which is a few seconds before the former one,
//even though the call was performed later - in any reasonable sense.
//The recorded history of events is obviously inconsistent with the real world.
order.setCancelationTime(Instant.now());

然后,当一个人不能依靠时间只朝一个方向发展时,就不可能执行时间敏感的事情,例如记录和分析事件历史。

上述帖子说System.nanoTime()是单调的(如果底层系统支持它)。因此,如果我想将我的代码基于java.time API,我需要在内部使用nanoTime的时钟来确保时间的单向流。也许这样的东西会起作用。还是不会?

public class MyClock() extends java.time.Clock {

    private final long adjustment;

    public MyClock() {
        long cpuTimeMillis = System.nanoTime()/1000000;
        long systemTimeMillis = System.currentTimeMillis();
        adjustment = systemTimeMillis - cpuTimeMillis;
    }

    @Override
    public long millis() {
        long currentCpuTimeMillis = System.nanoTime()/1000000;
        return currentCpuTimeMillis + adjustment;
    }
}   

它只是一个草图,而不是一个完整的时钟实现。我认为一个适当的实现也应该对构造函数中传递的另一个时钟执行调整,而不是直接针对currentTimeMillis()。

或者,是否已经在任何地方都可以使用这种单调时钟实现?我猜一定有很多人面临同样的问题。

结论

感谢您的鼓舞人心的评论和答案。评论中散布着几个有趣的观点,所以我将在这里总结一下。

1. 单调时钟

至于我原来的问题,是的,有可能有不受系统时间向后跳转影响的单调时钟。这种实现可以基于System.nanoTime(),正如我上面所建议的那样。过去,这种 aproach 曾经存在问题,但它在当今的现代系统上应该可以正常工作。例如,这种方法已经在Time4J库中实现,它们的单调时钟可以很容易地转换为java.time.Clock:

Clock clock = TemporalType.CLOCK.from(SystemClock.MONOTONIC);

2. 适当的系统时间控制

可以配置系统时间管理(unix/linux中的ntpd),以便系统时间几乎永远不会向后移动(如果有必要,它会减慢速度),然后可以依靠系统时间是单调的,并且在Java中不需要时钟魔术。

我会这样做,因为我的应用程序是服务器端的,我可以控制时间。实际上,我在我自己安装的实验环境中经历了异常(只有对该领域肤浅的了解),它只使用ntpdate客户端(如果时间不同步,它可以向后跳转),而不是ntpd(可以配置它,使其不会跳回去)。

3. 使用序列而不是时钟

当人们需要跟踪在什么之前发生的事情的强关系时,更安全的做法是从原子生成的序列中给出事件的序列号,而不是依赖于挂钟。一旦应用程序在多个节点上运行,它就成为唯一的选择(虽然不是我的情况)。


答案 1

正如@the8472所说,关键是要使机器(运行jvm的地方)上的时间同步正确。

如果您对客户端进行编程,那么依赖系统时钟确实很危险。但对于服务器,有一个解决方案 - 您可能需要考虑使用具有严格配置的NTP。

在这里,他们基本上解释了NTP会减慢时间,而不是将其向后设置。

这个NTP文档说:

有时,特别是当首次启动 ntpd 时,错误可能会超过 128 毫秒。如果将来相对于服务器的本地时钟时间超过 128 s,这有时可能会导致时钟向后设置。在某些应用程序中,此行为可能是不可接受的。如果命令行中包含 -x 选项,则永远不会步进时钟,只会使用压摆校正。


答案 2

请注意,nanoTime可能会单调增加,但它与墙壁时间的关系并不好,例如,由于休眠事件,VM挂起和类似的事情。

如果您开始在多个服务器上分发内容,那么在currentMillis上进行同步也可能由于时钟漂移而再次咬您。

也许您应该考虑控制服务器的系统时间。

或者将事件的相对顺序与应该记录它们的时间分开跟踪。


推荐