我在Windows 7上运行Java服务,该服务每天在SingleThreadScheduledExecutor
上运行一次。我没有给出太多的信息,因为它不是很关键,但是最近查看了一下数字,发现该服务每天大约漂移15分钟,这听起来实在太麻烦了。
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
long drift = (System.currentTimeMillis() - lastTimeStamp - seconds * 1000);
lastTimeStamp = System.currentTimeMillis();
}, 0, 10, TimeUnit.SECONDS);
该方法每10秒都会稳定地漂移
+110ms
。如果我每隔1秒运行一次,则漂移平均值为+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:不漂移(也不与Executor或Timer一起使用)
Windows:随Executor疯狂漂移,而不与Timer漂移
经过Java8和Java11测试。
有趣的是,如果您假设每秒11ms的漂移,那么您每天将获得950400ms的漂移,相当于每天
15.84 minutes
。因此,这是非常一致的。问题是:为什么?
为什么使用SingleThreadExecutor而不是使用Timer会发生这种情况。
Update1:在Slaw评论之后,我尝试了多种不同的硬件。我发现此问题在任何个人硬件上均未发现。仅对公司一。在公司硬件上,它也出现在Win10上,尽管数量少了一个数量级。
最佳答案
正如评论中指出的那样,ScheduledThreadPoolExecutor
的计算基于System.nanoTime()
。不管是好是坏,旧的Timer
API都在nanoTime()
之前,因此改用System.currentTimeMillis()
。
这里的差异可能看起来微妙,但比人们预期的更重要。与流行的看法相反,nanoTime()
不仅仅是currentTimeMillis()
的“更准确的版本”。 Millis被锁定为系统时间,而nanos则未锁定。 或as the docs put it:
在您的示例中,您没有遵循该指导来使值“有意义”-可以理解,因为ScheduledThreadPoolExecutor
仅使用nanoTime()
作为实现细节。但是最终结果是相同的,那就是您不能保证它将与系统时钟保持同步。
但是为什么不呢?秒是秒,对,所以两者应该从某个已知点保持同步?
好吧,从理论上讲,是的。但实际上,可能并非如此。
Taking a look at the relevant native code on 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,该API通过QueryPerformanceCounter
获得QueryPerformanceFrequency
定义的频率的“滴答声”来工作。该频率将保持不变,但是它基于的计时器以及Windows使用的同步算法根据配置,操作系统和底层硬件而有所不同。即使忽略以上内容,它也永远不会接近100%的准确度(它是基于电路板上某处相当便宜的晶体振荡器,而不是铯时间标准!)因此,随着NTP的发展,它会随着系统时间而漂移。与现实同步。特别地,this link提供了一些有用的背景,并加强了上面的内容:
(粗体是我的。)
对于Windows 7表现不佳的特定情况,请注意,在Windows 8+中,改进了TSC同步算法,并且
QueryPerformanceCounter
始终基于TSC(与Windows 7相反,它可能是TSC,HPET或ACPI PM计时器-后者尤其不准确。)我怀疑这是Windows 10上这种情况得到极大改善的最可能原因。话虽如此,上述因素仍然意味着您不能依靠
ScheduledThreadPoolExecutor
来保持“真实”时间的时间-它总是会漂移的。如果这种漂移是一个问题,那么这不是您可以在此情况下依赖的解决方案。旁注:在Windows 8+中,有一个
GetSystemTimePreciseAsFileTime
function,可提供QueryPerformanceCounter
的高分辨率以及系统时间的准确性。如果将Windows 7删除为受支持的平台,则在理论上可以将其用于提供System.getCurrentTimeNanos()
方法或类似方法,前提是其他受支持平台还存在其他类似的 native 功能。关于java - 为什么Java Scheduler在Windows上会出现明显的时间漂移?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/56571647/