代码之家  ›  专栏  ›  技术社区  ›  Frankie

Java调度程序时间漂移

  •  2
  • Frankie  · 技术社区  · 7 年前

    我在windows7上运行Java服务,每天一次 SingleThreadScheduledExecutor . 我从来没有给它太多,虽然它是非关键,但最近看了数字,看到服务漂移约15分钟,每天这听起来太多了,所以挖了它。

    Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
       long drift = (System.currentTimeMillis() - lastTimeStamp - seconds * 1000);
       lastTimeStamp = System.currentTimeMillis();
    }, 0, 10, TimeUnit.SECONDS);
    

    这种方法很容易漂移 +110ms +11ms

    有趣的是,如果我在一个 Timer() 数值与小于一毫秒的平均漂移非常一致。

    new Timer().schedule(new TimerTask() {
        @Override
        public void run() {
            long drift = (System.currentTimeMillis() - lastTimeStamp - seconds * 1000);
            lastTimeStamp = System.currentTimeMillis();
        }
    }, 0, seconds * 1000);
    

    Linux操作系统: 不漂移(执行器和定时器也不漂移)
    和执行者一起疯狂的漂流,而不是和计时器

    用Java8和Java11测试。

    15.84 minutes 每天。所以这是相当一致的。

    问题是:为什么?
    为什么单线程执行器会发生这种情况,而计时器却不会。

    根据Slaw的评论,我尝试了多种不同的硬件。我发现这个问题并没有出现在任何个人硬件上。只有一家公司。在公司硬件上,它也在Win10上表现出来,尽管数量级要少一些。

    0 回复  |  直到 7 年前
        1
  •  19
  •   Michael Berry    7 年前

    正如在评论中指出的那样 ScheduledThreadPoolExecutor 其计算依据 System.nanoTime() . 不管是好是坏 Timer nanoTime() ,等等 System.currentTimeMillis() 相反。

    纳米时间() 只是一个“更准确的版本” currentTimeMillis() . 或者 as the docs put it :

    此方法返回的值只有在计算在同一Java虚拟机实例中获得的两个值之间的差时才有意义。

    在您的示例中,您没有遵循此指导来让这些值“有意义”,这是可以理解的,因为 仅用于 纳米时间() 作为实现细节。但最终的结果是一样的,那就是你不能保证它会与系统时钟保持同步。

    但为什么不呢?秒就是秒,对吧,所以两者应该在某个已知点保持同步?

    Taking a look at the relevant native code on windows :

    LARGE_INTEGER current_count;
    QueryPerformanceCounter(&current_count);
    double current = as_long(current_count);
    double freq = performance_frequency;
    jlong time = (jlong)((current/freq) * NANOSECS_PER_SEC);
    return time;
    

    我们看到了 nanos() 使用 QueryPerformanceCounter API,由 查询性能计数器 QueryPerformanceFrequency . 这个频率将保持不变,但它所基于的计时器,以及windows使用的同步算法,因配置、操作系统和底层硬件而异。即使忽略了以上这些,它也是 将接近100%的准确度(它是基于一个相当便宜的晶体振荡器) 在黑板上,不是铯时间标准!)所以它会随着系统时间漂移,因为NTP会保持它与现实同步。

    this link 提供了一些有用的背景,并加强了上述观点:

    当您需要分辨率为1微秒或更高的时间戳时 ,选择“查询性能计数器”。

    查询性能计数器 总是 基于TSC(与windows7相反,windows7可能是TSC、HPET或ACPI-PM计时器,后者尤其不准确。)我怀疑这是windows10上情况得到极大改善的最可能原因。

    尽管如此,上述因素仍然意味着你不能依赖 与“真实”时间保持同步-它总是漂移的。如果这是一个问题,那么它不是一个解决方案,你可以依靠在这种情况下。

    GetSystemTimePreciseAsFileTime function 提供了高分辨率的 结合系统时间的准确性。如果Windows7作为一个受支持的平台被放弃,理论上可以用来提供 System.getCurrentTimeNanos() 方法或类似方法,假设其他支持的平台存在其他类似的本机函数。

        2
  •  3
  •   walen    6 年前

    CronScheduler 是一个矿井设计的项目,以防止时间漂移问题,同时它避免了一些老问题 Timer described in this post .

    用法示例:

    Duration syncPeriod = Duration.ofMinutes(1);
    CronScheduler cron = CronScheduler.create(syncPeriod);
    cron.scheduleAtFixedRateSkippingToLatest(0, 1, TimeUnit.MINUTES, runTimeMillis -> {
        // Collect and send summary metrics to a remote monitoring system
    });
    

    注意:这个项目实际上是受这个问题的启发。