JDK中nanoTime与currentTimeMillis时间漂移问题及实现原理问询
以起始点为基准,使用nanoTime与currentTimeMillis进行的时间测量值最多三分钟后会出现偏差。
该问题存在于JDK11及以上版本的Windows实现中,可能更早版本也受影响,但Linux似乎不受影响。由于最可能的问题根源在Windows,且认为currentTimeMillis是稳定的时间函数,而nanoTime存在偏差,因此想了解Java获取这两类时间戳的方式。
在Linux系统下,两者的差异源于毫秒转纳秒的换算方式,差异表现稳定且在毫秒级精度范围内。
由于两个值都是基于各自的起始点计算的,且起始点的获取间隔仅为几微秒,原本预期它们会像Linux下一样保持毫秒级精度。计时器的节奏异常本不应造成影响,因为该计时器仅用于提供缓慢、可读的测量进度,无需精确计时。
import java.text.*; import java.util.*; import java.util.concurrent.*; public class ClockWatch { static final long StartNS = System.nanoTime(); static final long StartMS = System.currentTimeMillis(); public static void main(String[] args) { NumberFormat nf = NumberFormat.getNumberInstance( Locale.US); nf.setMaximumFractionDigits( 3); nf.setGroupingUsed( true); DateFormat df = new SimpleDateFormat( "HH:mm:ss"); df.setTimeZone( TimeZone.getTimeZone( "GMT")); ScheduledExecutorService ses = new ScheduledThreadPoolExecutor( 1); Runnable compareJob = () -> { long nowNS = System.nanoTime() - StartNS; long nowMS = System.currentTimeMillis() - StartMS; long tookNS = ( System.nanoTime() - StartNS) - nowNS; long diffNS = 1_000_000 * nowMS - nowNS; double driftPercent = 100.0 * diffNS / nowNS; System.out.println( df.format( new Date( nowMS)) + " currentTimeMillis is " + ( diffNS < 0 ? "" : " ") + nf.format( 1e-6 * diffNS) + " ms faster than nanoTime, took " + nf.format( tookNS) + " ns, drift " + nf.format( driftPercent) + " % since start" ); }; ses.scheduleAtFixedRate( compareJob, 1, 3, TimeUnit.SECONDS); } }
我对此问题感到困扰的原因是,希望复用已有的纳秒时间戳,将其转换为等效的currentTimeMillis值,而无需调用currentTimeMillis方法。我知道,在Windows最新硬件上该方法仅需4纳秒。
所有机器已运行多日,借助chrony和ntp实现了亚毫秒级的时钟精度,因此不会出现突然跳变。
我了解nanoTime和currentTimeMillis的特性,或许会改用Instant.now替代两者。
核心需求是使用log4j2(配置了完整时间戳)记录事件。多线程异步向同一文件日志,这是一台大型机器。在某些情况下,由于log4j处理不及时,日志线程会出现阻塞,导致日志线程变慢。为缓解此问题,我将log4j调用移至由另一个线程处理的队列中。由于预期该线程会产生队列延迟,我希望在日志消息中添加另一个时间戳,反映事件被加入日志队列的时间。对于这个内部时间戳,我已有一个纳秒时间戳,希望将其复用为与log4j时间戳匹配的格式。
目前我通过获取另一个currentTimeMillis时间戳解决了问题,但这需要在许多对象中添加一个long字段并增加一次测量,这两者都是我想避免的。由于需要测量值与log4j使用的时间兼容,我现在似乎需要尝试使用Instant.now。
内容的提问来源于stack exchange,提问作者Ralf H

