clojure.core.async/timeout
使用 System/currentTimeMillis
来计算其截止日期(见这里和这里)。虽然 System/currentTimeMillis
的文档字符串并未明确指出,但它明显不适合此目的,因为它返回操作系统报告的当前时间。由于该时间可以任意更改(例如,通过NTP进行校正),最终的截止日期计算可能会有不一致的结果。这种行为是非常有意为之的,例如,参见此工单,其中建议尽可能将System/currentTimeMillis
切换到单调时钟。它被以下评论拒绝:
需要System.currentTimeMillis()返回自纪元以来的毫秒数,并应反映系统/挂钟时间的外部更改。CLOCK_MONOTONIC_COARSE无法提供,因此不能用于此目的。
此外,java.util.concurrent.ScheduledExecutorService
的文档字符串对此表述相当明确:
所有调度方法接受相对延迟和周期作为参数,而不是绝对时间或日期。将表示为Date的绝对时间转换为所需形式的简单操作。例如,要安排在未来某个日期,您可以使用:schedule(task, date.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS)。但是,请注意,由于网络时间同步协议、时钟漂移或其他因素,相对延迟的到期不一定与任务启用的当前Date一致。
由于我们在这里所关心的只是差分,因此切换到 System/nanoTime
似乎更为可取。遗憾的是,其方法文档 同样 并没有使其属性表现得非常明显。它并没有提到它是一个单调时钟,而是仅仅通过指出
返回运行中的Java虚拟机的较高分辨率时间源的当前值,以纳秒为单位。此方法只能用于测量经过的时间,与系统或墙钟时间的任何其他概念无关。 [...] 此方法返回的值仅在从同一Java虚拟机实例中获取的两个值之间的差值计算时才有意义。
一个使用它的案例是OpenJDK对 java.util.concurrent.ScheduledThreadPoolExecutor
的实现,其截止时间计算方法 如此做,以及其getDelay
实现。
此外——如果可以以此为依据——StackOverflow 上的许多帖子也讨论了这个问题,并得出相同的结论,例如这个帖子
尽管如此,对于实现定时阻塞、周期等待、超时等,仍然应该首选 nanTime() 而不是 currentTimeMillis(),因为后者的“时间倒退”现象更容易发生(例如,由于对服务器时间的校正),也就是说,currentTimeMillis() 完全不适合测量时间间隔。请参阅此答案以获取更多信息。
另请参阅有关 core.cache
的类似问题。