精度不够,尤其毫秒级以下场景;它依赖系统时钟,受休眠、NTP调整等影响,适合粗略估算,微基准测试应改用System.nanoTime()。

用 System.currentTimeMillis() 测耗时,精度够吗?
不够,尤其在毫秒级以下场景。它返回的是自 1970-01-01 00:00:00 UTC 的毫秒数,底层依赖系统时钟,受系统休眠、NTP 调整、时钟漂移影响,两次调用差值可能跳变或倒流。
适合粗略估算:接口响应、批处理整体耗时、日志打点等对精度要求不高的地方。
- 别用于微基准测试(比如对比两个
for循环快慢) - 别在高并发短任务中依赖它判断“是否超时 10ms”
- Windows 上最小分辨率通常为 15–16ms,Linux 一般 1–10ms,实际看内核配置
System.currentTimeMillis() 正确用法和常见错误
核心就两步:起始记一次,结束再记一次,相减。但容易错在时间单位混淆、顺序颠倒、没考虑异常路径。
- 必须用
long接收,int会溢出(约 24.8 天就翻转) - 起始时间一定要在业务逻辑开始前获取,不是方法入口就取——比如有参数校验,应在校验后、主逻辑前
- 结束时间必须在所有路径都覆盖到:
try里取了,catch和finally里也得取,否则异常时耗时统计为空或错乱 - 别写成
endTime - startTime 来防倒流——它真可能负,说明系统时钟被调了,此时你该报警,而不是忽略
示例:
立即学习“Java免费学习笔记(深入)”;
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
long start = System.currentTimeMillis();
try {
doSomething();
} finally {
long end = System.currentTimeMillis();
log.info("耗时 {} ms", end - start);
}什么时候该换 System.nanoTime()?
当你需要测代码段真实执行时间,且不关心“墙上时间”,只关心 CPU 花了多少纳秒——比如性能压测、算法比对、JVM GC 暂停分析。
-
System.nanoTime()是单调递增的,不受系统时钟调整影响 - 单位是纳秒,但**不代表精度是纳秒**——实际分辨率取决于硬件和 OS,通常在 10ns–1000ns 之间
- 不能和
System.currentTimeMillis()混用,也不能用来算“当前时间” - 开销略高于
currentTimeMillis(),但现代 JVM 差异极小,可忽略
示例(测一段计算):
long start = System.nanoTime();
int result = computeHeavyValue();
long end = System.nanoTime();
log.debug("computeHeavyValue took {} ns", end - start);更靠谱的耗时封装建议
手写 start/end 容易漏、难复用、日志格式不统一。简单封装一层能规避多数坑。
- 用
try-with-resources自动记录,避免finally忘写 - 把耗时单位、标签、异常信息一并塞进日志上下文,别只打个数字
- 别封装成静态工具类直接返回
long——丢失上下文,后期加 traceId 或采样率控制很麻烦 - 如果项目已用 Micrometer 或 Dropwizard Metrics,优先走
Timer,它内部自动选nanoTime,还带分布统计
一个轻量封装示意:
public class Timing implements AutoCloseable {
private final String tag;
private final long start;
<pre class='brush:java;toolbar:false;'>public Timing(String tag) {
this.tag = tag;
this.start = System.nanoTime();
}
@Override
public void close() {
long elapsed = System.nanoTime() - start;
log.info("{} took {} ms", tag, TimeUnit.NANOSECONDS.toMillis(elapsed));
}} // 使用: try (Timing t = new Timing("db-query")) { queryDatabase(); }
测耗时最常被忽略的,是「是否包含 GC 停顿」和「是否跨线程/跨 JVM 进程」。这两个问题 System.currentTimeMillis() 和 System.nanoTime() 都回答不了,得靠 JFR、AsyncProfiler 或分布式 tracing 工具。

















