system.currenttimemillis() 不是计时器而是挂钟时间,精度受系统时钟粒度限制(linux 1–10ms,windows 15–16ms),易受ntp校正、手动改时、虚拟机休眠等干扰,单次测量不可靠,应使用long类型、try-finally保障执行、多次采样取平均。

System.currentTimeMillis() 的误差不是 bug,而是它本来的设计定位——它提供的是“挂钟时间”,不是“计时器”。想靠它测准一段代码跑了多久,就像用体重秤量温度,工具不对,结果自然不可信。
底层时钟频率决定精度上限
它的返回值来自操作系统级时间源:
- Linux 多数场景下依赖
gettimeofday(),实际分辨率在 1–10 毫秒之间,取决于内核配置和硬件定时器(如 HPET 或 TSC) - Windows 旧版本常见 15–16 毫秒粒度;新系统虽有改善,但默认仍不保证亚毫秒级响应
- 两次调用间隔若短于该粒度,返回值完全相同 → 耗时显示为 0,不代表没执行,只是系统“看不见”
系统行为会直接污染测量结果
它反映的是墙上时间,而非 CPU 执行时间,因此极易被外部干预扭曲:
- NTP 同步可能让时间突然回拨(比如校正 -200ms),导致
end - start出现负数 - 用户手动改系统时间、虚拟机休眠/恢复、容器暂停等,都会造成时间戳跳变
- 跨节点打点时,不同机器的本地时钟偏差无法对齐,链路总耗时相加毫无意义
实战中哪些写法会让误差更明显
这些常见操作看似合理,实则放大了底层缺陷:
- 测空循环或 getter 方法:真实耗时常低于 1ms,但输出全是 0 或随机跳变的 15ms
- 高并发下密集调用:HotSpot 在某些 Windows 环境中存在内部锁竞争,百次调用开销可达单线程数百倍
- 把
int当时间变量:跨天后溢出,startTime变成负数,差值彻底失真 - 只在 try 块里记 start,异常抛出后 end 没执行 → 变量未定义,编译都过不了
不是不能用,而是得守住底线
如果受限于框架或历史代码暂时无法替换,至少做到这三点:
- 始终用 long 接收,避免整型溢出
- 用 try-finally 包裹待测逻辑,确保 end 必然执行
- 别信单次结果:跑 100 次取平均或看分布,比盯着一个 8ms 还是 15ms 更有参考价值











