Как указано в комментариях, ScheduledThreadPoolExecutor
основывает свои расчеты на System.nanoTime()
.Что бы там ни было, старый Timer
API, однако, предшествовал nanoTime()
, и поэтому вместо него используется System.currentTimeMillis()
.
Разница здесь может показаться незначительной, но более существенной, чем можно было ожидать.Вопреки распространенному мнению, nanoTime()
является , а не просто «более точной версией» currentTimeMillis()
. Миллис привязан к системному времени, а нанос - нет. Или , как написано в документах :
Этот метод может использоваться только для измерения прошедшего времении не связан с каким-либо другим понятием системного или настенного времени.[...] Значения, возвращаемые этим методом, становятся значимыми, только когда вычисляется разница между двумя такими значениями, полученными в одном и том же экземпляре виртуальной машины Java.
В вашем примере вы не следуете этому руководству, чтобы значения были "значимыми" - понятно, потому что ScheduledThreadPoolExecutor
использует только nanoTime()
в качестве детали реализации.Но конечный результат тот же, что вы не можете гарантировать, что он будет синхронизирован с системными часами.
Но почему бы и нет?Секунды - это секунды, верно, поэтому они должны оставаться синхронизированными с определенной известной точки?
Ну, в теории да.Но на практике, вероятно, нет.
Взгляните на соответствующий нативный код в Windows :
LARGE_INTEGER current_count;
QueryPerformanceCounter(¤t_count);
double current = as_long(current_count);
double freq = performance_frequency;
jlong time = (jlong)((current/freq) * NANOSECS_PER_SEC);
return time;
Мы видим, что nanos()
использует QueryPerformanceCounter
API, который работает путем QueryPerformanceCounter
получения "тиков" частоты, определенной QueryPerformanceFrequency
.Эта частота будет оставаться идентичной, но таймер, на котором он работает, и его алгоритм синхронизации, который использует Windows, различаются в зависимости от конфигурации, ОС и аппаратного обеспечения.Даже игнорируя вышесказанное, он никогда не будет близок к 100% точности (он основан на относительно дешевом кварцевом генераторе где-то на плате, а не на стандарте времени Цезия!)будет расходиться с системным временем, так как NTP поддерживает его синхронизацию с реальностью.
В частности, эта ссылка дает некоторый полезный фон и усиливает вышеприведенный понтон:
Если вам нужны метки времени с разрешением 1 микросекунда или лучше и вам не нужно синхронизировать метки времени с внешним эталоном времени , выберите QueryPerformanceCounter.
(Болтание мое.)
В вашем конкретном случае плохой работы Windows 7 обратите внимание, что в Windows 8+ был улучшен алгоритм синхронизации TSC, и QueryPerformanceCounter
был всегда на основе TSC (в отличие от Windows 7, где это может быть таймер TSC, HPET или ACPI PM - последний из которых особенно неточен). Я подозреваю, что это наиболееВероятная причина того, что ситуация значительно улучшается в Windows 10.
При этом вышеперечисленные факторы по-прежнему означают, что вы не можете полагаться на ScheduledThreadPoolExecutor
, чтобы идти в ногу с «реальным» временем - оно всегда будетдрейфовать.Если это отклонение является проблемой, то это не решение, на которое вы можете положиться в этом контексте.
Примечание: в Windows 8+ есть функция GetSystemTimePreciseAsFileTime
который предлагает высокое разрешение QueryPerformanceCounter
в сочетании с точностью системного времени.Если бы Windows 7 была отброшена как поддерживаемая платформа, теоретически это можно было бы использовать для предоставления метода System.getCurrentTimeNanos()
или аналогичного, предполагая, что существуют другие аналогичные собственные функции для других поддерживаемых платформ.