先说结论,ScheduledExecutorService.scheduleAtFixedRate只能保证执行的频率,并不能精确保证两次执行的时间间隔。
之前有个定时数据采集的任务,使用ScheduledExecutorService.scheduleAtFixedRate每隔10秒钟触发一次,将数据采集并保存下来。为了便于查询,以触发采集的时间(秒)向下取整作为主键,例如当前时间08:21:18触发,那么这次采集的数据的key就是08:21:10。
后来发现偶尔会有某个时间点的数据没有采集到,而某个时间点又会采集两次,比如11:27:20,11:27:30,11:27:50,11:27:50(缺少11:27:40)。
经过排查,发现任务启动的时间是11:27:29,所以理论上来说,应该在11:27:39,11:27:49,11:27:59都会触发一次,但从日志上看,实际上的触发时间是11:27:29,11:27:39,11:27:50,11:27:59,其中本该在11:27:49的采集任务延迟了一秒才被触发。
这个问题本质的原因是ScheduledThreadPool利用一个DelayQueue来保存下一次需要被触发的任务。DelayQueue使用System.nanoTime()来计算任务还有多久被触发。
在x86系统中,System.nanoTime()是跟CPU内置的counter紧密相关,那么问题来了, 在一个多核的操作系统中,第一次任务可能被CPU core1安排,第二次任务可能被CPU core2安排,两个核的counter很有可能是不同的,那么两次任务的System.nanoTime()相减就不能保证是精确的10秒钟。