Java 中是否有任何一致的(单调的)时钟实现?
Is there any consistent (monotonic) Clock implementation in Java?
默认的 java.time.Clock 实现基于 System.currentTimeMillis()。正如这里所讨论的那样,
Monotonically increasing time in 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());
当一个人不能依赖时间只在一个方向上流动时,就不可能执行对时间敏感的事情,比如记录和分析事件历史。
前面提到的post说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;
}
}
这只是一个草图,不是完整的 Clock 实现。而且我认为正确的实现还应该针对构造函数中传递的另一个时钟执行调整,而不是直接针对 currentTimeMillis()。
或者,是否已经有这样一个单调的 Clock 实现?我想肯定有很多人遇到同样的问题。
结论
感谢鼓舞人心的评论和回答。评论中散落着几个有趣的点,所以我在这里总结一下。
1.单调时钟
至于我原来的问题,是的,可以有不受系统时间影响的单调时钟向后跳。这样的实现可以基于我上面建议的 System.nanoTime() 。过去这种方法曾经存在问题,但它在当今的现代系统上应该可以正常工作。这种方法已经在 Time4J 库中实现,它们的单调时钟可以很容易地转换为 java.time.Clock:
Clock clock = TemporalType.CLOCK.from(SystemClock.MONOTONIC);
2。适当的系统时间控制
可以配置系统时间管理(unix/linux 中的 ntpd),这样系统时间几乎永远不会倒退(如果有必要,它只会减慢速度),然后就可以依赖系统时间了是单调的,在 Java 中不需要时钟魔法。
我会走这条路,因为我的应用程序是服务器端的,我可以控制时间。其实我是在自己安装的实验环境中遇到异常的(对领域了解不多),而且是用 ntpdate 客户端(时间快可以往回跳)不同步),而不是 ntpd(可以配置为不跳回)。
3。使用序列而不是时钟
当需要跟踪发生在什么之前的强关系时,从原子生成的序列中给出事件序列号而不是依赖挂钟会更安全。一旦应用程序在多个节点上 运行 (虽然我的情况不是这样),它就成为唯一的选择。
请注意,nanoTime 可能会单调增加,但它与挂钟时间的关系并不好,例如由于休眠事件、VM 挂起和类似事件。
如果你开始在多个服务器上分发东西,那么由于时钟漂移,在 currentMillis 上同步也可能会再次困扰你。
也许您应该考虑控制服务器的系统时间。
或者跟踪事件的相对顺序,与事件的记录时间分开。
正如@the8472所说,关键是让机器(运行jvm的地方)的时间同步正确。
如果您编写客户端程序,那么依赖系统时钟确实很危险。
但对于服务器,有一个解决方案 - 您可能需要考虑使用具有严格配置的 NTP。
In here they basicly explain that NTP会减慢时间而不是倒退。
并且 this NTP documentation 表示:
Sometimes, in particular when ntpd is first started, the error might
exceed 128 ms. This may on occasion cause the clock to be set
backwards if the local clock time is more than 128 s in the future
relative to the server. In some applications, this behavior may be
unacceptable. If the -x option is included on the command line, the
clock will never be stepped and only slew corrections will be used.
默认的 java.time.Clock 实现基于 System.currentTimeMillis()。正如这里所讨论的那样, Monotonically increasing time in 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());
当一个人不能依赖时间只在一个方向上流动时,就不可能执行对时间敏感的事情,比如记录和分析事件历史。
前面提到的post说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;
}
}
这只是一个草图,不是完整的 Clock 实现。而且我认为正确的实现还应该针对构造函数中传递的另一个时钟执行调整,而不是直接针对 currentTimeMillis()。
或者,是否已经有这样一个单调的 Clock 实现?我想肯定有很多人遇到同样的问题。
结论
感谢鼓舞人心的评论和回答。评论中散落着几个有趣的点,所以我在这里总结一下。
1.单调时钟
至于我原来的问题,是的,可以有不受系统时间影响的单调时钟向后跳。这样的实现可以基于我上面建议的 System.nanoTime() 。过去这种方法曾经存在问题,但它在当今的现代系统上应该可以正常工作。这种方法已经在 Time4J 库中实现,它们的单调时钟可以很容易地转换为 java.time.Clock:
Clock clock = TemporalType.CLOCK.from(SystemClock.MONOTONIC);
2。适当的系统时间控制
可以配置系统时间管理(unix/linux 中的 ntpd),这样系统时间几乎永远不会倒退(如果有必要,它只会减慢速度),然后就可以依赖系统时间了是单调的,在 Java 中不需要时钟魔法。
我会走这条路,因为我的应用程序是服务器端的,我可以控制时间。其实我是在自己安装的实验环境中遇到异常的(对领域了解不多),而且是用 ntpdate 客户端(时间快可以往回跳)不同步),而不是 ntpd(可以配置为不跳回)。
3。使用序列而不是时钟
当需要跟踪发生在什么之前的强关系时,从原子生成的序列中给出事件序列号而不是依赖挂钟会更安全。一旦应用程序在多个节点上 运行 (虽然我的情况不是这样),它就成为唯一的选择。
请注意,nanoTime 可能会单调增加,但它与挂钟时间的关系并不好,例如由于休眠事件、VM 挂起和类似事件。
如果你开始在多个服务器上分发东西,那么由于时钟漂移,在 currentMillis 上同步也可能会再次困扰你。
也许您应该考虑控制服务器的系统时间。
或者跟踪事件的相对顺序,与事件的记录时间分开。
正如@the8472所说,关键是让机器(运行jvm的地方)的时间同步正确。
如果您编写客户端程序,那么依赖系统时钟确实很危险。 但对于服务器,有一个解决方案 - 您可能需要考虑使用具有严格配置的 NTP。
In here they basicly explain that NTP会减慢时间而不是倒退。
并且 this NTP documentation 表示:
Sometimes, in particular when ntpd is first started, the error might exceed 128 ms. This may on occasion cause the clock to be set backwards if the local clock time is more than 128 s in the future relative to the server. In some applications, this behavior may be unacceptable. If the -x option is included on the command line, the clock will never be stepped and only slew corrections will be used.