为什么在大时间窗口上计算的经过时间在 System.currentTimeMillis 与 System.nanoTime 之间有高达 100+ 毫秒的差异

问题描述 投票:0回答:1

我正在尝试创建一个可在同一 JVM 中进行微秒进动排序的 KSUID,因为 currentTimeMillis 仅提供毫秒精度,考虑将 currentTimeMillis() 和 nanoTime() 一起使用,使用 nanoTime 差值收集第一个瞬间和第二个瞬间开始的值计算自第一个时间点以来经过的微秒数。使用从 nanoTime 计算出的经过时间并将其添加到初始即时毫秒,以便我们可以在 JVM 启动后的任何即时时间(均在同一 JVM 内)以微秒精度计算 Epoch。

//静态收集初始nanoTime和currentMillis

        final long initTimeNanos = System.nanoTime(); final long initTimeMillis = System.currentTimeMillis();

//从这里开始,当需要微/纳秒精度的当前时间时,从nanoTime计算经过的时间并将经过的毫秒添加到initialMillis,其余经过的时间给出微秒精度的时间

final long elapsedNanoTime = System.nanoTime() - initTimeNanos; final double elapsedMillisFromNanoTime = elapsedNanos / (1000000.0);

//自JVM启动以来单调递增的时间戳毫秒(Epoch,我们可能不称其为Epoch)

final long computedCurrentTimeMillis = initTimeMillis + elapsedMillisFromNanoTime; final long nanosPrecision = elapsedNanos % 1000000;
//nanoTime 的附加时间精度

考虑使用这些值来生成单调递增的 KSUID,它可以近似表示当前时间,但保证根据同一 JVM 内的创建时间进行排序,因为 currentTimeMillis 不提供单调递增的时间戳保证,考虑使用这种方法来单调生成增加大约接近真实时间戳的时间戳(可能有几毫秒的差异,除非对纪元时间进行闰秒调整)。使用 epoch 和 nanoTime 计算出的经过时间预计有几毫秒的差异,但实际差异非常频繁地变化,当我在测试以下运行 48 小时时,观察到这两者之间的差异高达 150 毫秒。大多数情况下,使用 nanoTime 计算的经过时间高于使用 currentTimeMillis 计算的经过时间,并且观察到的时间范围为 -2 毫秒到 +150 毫秒。

            final long elapsedMillis = System.currentTimeMillis() - initTimeMillis;
//来自System.currentTimeMillis()的经过时间毫秒
            final double varianceMillis = elapsedMillisFromNanoTime - elapsedMillis;
//经过时间变化

我是否遗漏了有关 JVM 时间保证的任何内容,计算单调递增近似时间戳的方法是否错误?(这不会用作系统中的真实纪元,仅用于生成 UUID,UUID 也代表同一 JVM 内的近似时间戳即时)。

//测试类

package org.example;

import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args) throws Exception {
        final long initTimeNanos = System.nanoTime();
        final long initTimeMillis = System.currentTimeMillis();
        System.out.println("Nanos: " + initTimeNanos);
        System.out.println("Millis: " + initTimeMillis);

        while (true) {
            final long currentNanos = System.nanoTime();
            final long elapsedNanos = currentNanos - initTimeNanos;
            final double elapsedMillisFromNanos = elapsedNanos / 1000000.0;
            final long elapsedMillis = System.currentTimeMillis() - initTimeMillis;
            final double varianceMillis = elapsedMillisFromNanos - elapsedMillis;
            if (Math.abs(varianceMillis) > 1) {
                System.out.printf("\nVariance Observed: %.6f\n", varianceMillis);
                System.out.printf("Elapsed Time: %.6fms (from System.nanoTime)\n", elapsedMillisFromNanos);
                System.out.printf("Elapsed Time: %dms (from System.currentTimeMillis)\n", elapsedMillis);
            }
            if (elapsedMillis > TimeUnit.HOURS.toMillis(48)) {
                break;
            }
            Thread.sleep(5000);
            System.out.print(".");
        }
    }
}

为什么经过的时间方差不断变化? 如果 JVM 连续运行一年,我们可以预期的最大方差是多少(任何 JVM 对此上限或下限的保证,使用 MAC 和 Windows 进行测试,MAC 给出了方差的缓慢增加,Windows 的速度要快得多)?

我预计经过的时间变化小于 10 毫秒,变化变化频率较低。 但实际观察是不断变化的方差,在 48 小时内观察到最多 150 毫秒的上下变化

java time timestamp epoch nanotime
1个回答
0
投票

一种解释是基于 NTP 的时间涂抹。更一般地说,纳米时间和 cTM 测量完全不同的东西,你不能混合它们。

nanotime 有一个任意的 0 点(获得 0 的 nanoTime 没有特殊意义),因此,除了将它返回的内容与不同 nanoTime 调用的结果进行比较之外,根本没有必要调用它。 nanoTime 跟踪经过的时间,就是这样。

System.cTM 获取系统时钟。如果您使用 posix

date
命令或在系统设置中编辑系统的时间设置,这对
nanoTime
没有影响,但会更改
System.cTM
返回的内容。 cTM 通常也比 nanoTime 慢得多。 cTM 还有一个明确定义的 0 - 这意味着 UTC 1970 年 1 月 1 日午夜。

问题:“我希望当前时间符合纳秒精度的系统时钟”在 JVM 上是不可能

时间涂抹是指某些网络时间守护程序注意到您的系统时钟略有偏差,并且不只是将系统时钟编辑到正确的时间,而是引入了涂抹:假设您比实时时间“提前”了 400 毫秒。 NTP 可以将您的时钟向后设置 400 毫秒,但许多日志记录系统假设 System.cTM 不会向后移动(这是一个不正确但广泛应用的假设)。

可以通过让时间慢一些来“修复”:每经过 100 毫秒,NTP 守护进程就会将时钟“保持”在同一毫秒上一毫秒。每 100 毫秒就赶上 1 毫秒,因此在 400,000 毫秒(仅 400 秒)内,时钟恢复与网络同步,并且日志记录根本不受影响。

但是,这显然会完全破坏 System.cTM 和 nanoTime 之间的任何关系!

大多数 NTP 都会像这样涂抹(它们还会向前涂抹 - 如果您的系统时钟落后,它不仅仅会向前跳跃:这使得日志撒谎(使得 看起来 两个事件之间的间隙比实际情况大得多) ,因此每 100 毫秒,NTP 就会使时钟跳过一毫秒,类似这样的事情,以赶上。

© www.soinside.com 2019 - 2024. All rights reserved.