system.currenttimemillis() 在 ntp 跳变时会突变,导致时间不单调;应改用 system.nanotime() 测量耗时,绝对时间需检测跳变或启用 ntp slewing 模式。

System.currentTimeMillis() 在 Linux 系统开启 NTP 时间同步时,遇到时间跳变(如向后或向前大幅调整)会直接反映系统时钟变化,导致返回值不单调、出现回退或突增,这对依赖时间差的逻辑(如超时判断、定时调度、日志打点)非常危险。
理解问题根源:NTP 跳变 vs. 慢速 slewing
NTP 默认行为分两种:
- step mode(跳变模式):当系统时钟偏差超过阈值(如 128ms),ntpd 或 systemd-timesyncd 可能直接“跳”时间——秒级甚至分钟级突变,currentTimeMillis() 立刻跟随变化;
- slew mode(平滑调整):通过 adjtimex() 微调时钟频率,让时间缓慢追上参考源,此时 currentTimeMillis() 单调递增,但速率略快/略慢(最大 ±500ppm)。
Java 不区分这两种模式,只读取内核的 CLOCK_REALTIME,因此跳变时无法感知、也无法补偿。
避免依赖 currentTimeMillis() 做时间差计算
凡是用 (t2 - t1) 判断耗时、超时、过期的代码,在跳变下都可能出错。例如:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
long start = System.currentTimeMillis();
// ... 执行业务
if (System.currentTimeMillis() - start > 5000) { // 可能因时间回拨误判超时
throw new TimeoutException();
}
正确做法是改用 System.nanoTime() 测量经过时间:
- nanoTime() 基于单调递增的硬件计时器(如 TSC),不受 NTP 调整影响;
- 它只适合测间隔,不能转成绝对时间戳;
- 注意:nanoTime 精度高但不保证绝对准确,仅用于相对差值。
需要绝对时间戳时,主动防御跳变
如果必须用 currentTimeMillis()(如生成日志时间、缓存过期时间),需检测并缓解跳变:
- 记录上次调用值,对比当前值,若差值异常(如 +60000ms),可拒绝、告警或降级为 nanoTime + 基准偏移;
- 使用第三方库如 Elasticsearch 的 RealTimeClock,它内部做跳变检测与平滑;
- 生产环境建议配置 NTP 使用 slewing 模式(如 ntpd 加 -x 参数,chronyd 设置 makestep 0),禁用跳变。
更可靠的替代方案:使用 java.time.Clock
Java 8+ 提供 Clock 抽象,便于测试和替换:
- 默认 Clock.systemUTC() 仍走系统时间,受跳变影响;
- 可自定义 Clock 实现,例如包装 nanoTime 并维护一个“软时间”,只在未检测到跳变时更新;
- 关键服务建议注入 Clock 实例,而非硬编码 currentTimeMillis(),方便统一管控。
不复杂但容易忽略:时间跳变不是 Java 的 Bug,而是系统时钟行为本身。关键是把“时间测量”和“时间表示”分开设计,该用 nanoTime 的地方别用 currentTimeMillis(),该加防护的地方别裸用。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










