我让Java服务在Windows 7上运行,每天在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:不漂移(也不使用执行器,也不使用计时器)
窗口:像疯了一样和执行器在一起,而不是用计时器
用Java8和Java11进行测试。
有趣的是,如果假设漂移为每秒11毫秒,那么每天就会有950400毫秒的漂移,相当于每天的15.84 minutes。所以这很一致。
的问题是:为什么?
为什么这种情况会发生在SingleThreadExecutor上,而不是在计时器上。
Update1:继Slaw的评论后,我尝试了多个不同的硬件。我发现这个问题在任何个人硬件上都没有表现出来。只在公司一号上。在公司硬件上,它也可以在Win10上显示,不过数量级要小一些。
发布于 2019-06-14 12:31:16
正如评论中指出的那样,ScheduledThreadPoolExecutor的计算基于System.nanoTime()。无论是好是坏,旧的Timer API都会取代nanoTime(),因此会使用System.currentTimeMillis()。
这里的差别似乎很微妙,但比人们预期的要重要得多。与流行的观点相反,nanoTime()不仅仅是currentTimeMillis()的“更精确的版本”。Millis被锁定在系统时间,而nanos则不是。或就像医生说的
这种方法只能用于测量经过的时间,而与系统或挂钟时间的任何其他概念无关。.方法返回的值只有在计算在同一实例中获得的两个此类值之间的差异时才有意义。
在您的示例中,对于这些值是否“有意义”,您没有遵循本指南--这是可以理解的,因为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来保持“实时”时间--它总是在漂移。如果这种漂移是一个问题,那么在这种情况下,它就不是一个可以依赖的解决方案。
附带注意:在8+中,有一个,它结合了系统时间的精确性,提供了高分辨率的QueryPerformanceCounter。如果将Windows 7作为受支持的平台删除,理论上可以使用它来提供System.getCurrentTimeNanos()方法或类似的方法,假设其他受支持的平台存在其他类似的本机函数。
发布于 2020-01-29 13:51:59
CronScheduler是一个针对时间漂移问题而设计的项目,同时它避免了旧的Timer类在这篇文章中描述的一些问题。
示例用法:
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
});注意:这个项目实际上是受这个StackOverflow问题的启发。
https://stackoverflow.com/questions/56571647
复制相似问题