system.currenttimemillis() 高频调用会引发 jni 切换和多线程竞争,单次耗时 20–100 纳秒,远高于 object 创建或 hashcode();优化手段包括缓存时间戳、批量获取、按用途选用 nanotime/instant/atomiclong 等替代方案。

System.currentTimeMillis() 看似轻量,但在高频率调用下确实会带来可观开销,尤其在高并发、低延迟场景中可能成为性能瓶颈。
单次调用本身不慢,但高频+多线程时放大问题
单次调用耗时约 20–100 纳秒(现代 CPU),比创建一个 Object(约 10 ns)或调用 hashCode()(约 5 ns)还重。问题不在于一次,而在于:
- 每次都触发 JNI 调用 → 用户态到内核态切换(如 Linux 下
clock_gettime(CLOCK_REALTIME)) - 多线程争抢时,底层可能因共享时钟源或 VDSO 机制不完善产生隐式竞争
- 实测显示:100 次并发调用耗时是串行调用的 250 倍以上
典型高开销场景
- 日志框架在每条日志中独立调用(如 SLF4J 默认打点)
- 流处理中每个事件都取时间戳(Kafka 消费、Flink operator)
- 限流/防抖逻辑放在循环或 getter 内
- 毫秒级定时轮询(
while (now )
关键影响因素
- 虚拟化环境(云服务器、容器)中,时间获取常被虚拟层拦截,延迟显著上升
- 操作系统时间源选择(如 HPET 比 TSC 慢得多),Java 无法控制
- JDK 版本差异:JDK 8u232+ 对
currentTimeMillis()底层做了 VDSO 优化,但效果依赖 OS 支持
不是所有地方都需要实时精度
多数业务场景(监控采样、请求打标、过期判断)容忍 ±1–5ms 误差。真正需要毫秒级绝对时间的,其实远少于我们默认使用的次数。
H3:缓存时间戳是最常用且有效的优化手段
- 用
volatile long存储,后台线程每 1–5ms 更新一次,业务代码直接读变量 - 无需锁,
volatile保证可见性;更新间隔折中:太短增加调度负担,太长误差超标 - 示例:
private static volatile long cachedTime = System.currentTimeMillis(); // 后台线程:Thread.sleep(1) + 更新
H3:批量获取比分散调用更省
- Web 请求中,在 Filter 或 Interceptor 统一取一次时间,注入 RequestContext
- 批处理任务(如 Kafka batch consume)只在拉取后记一次起始时间,整批复用
- 避免在 for 循环、对象构造、getter 中隐式调用
H3:按用途选对 API,别“一把尺子量所有”
- 测耗时 → 用
System.nanoTime():无系统调用、不受 NTP 调整影响、单调递增 - 建模/持久化时间点 → 用
Instant.now().toEpochMilli():语义清晰、JDK 8+ 有内部优化 - 只需顺序或唯一性 → 用
AtomicLong.incrementAndGet()替代时间戳,彻底规避系统时钟
H3:慎用定时器更新方案
-
ScheduledExecutorService定时刷新看似简单,但引入调度器开销和线程管理成本 - 更轻量做法:纯
while(true) { cachedTime = System.currentTimeMillis(); Thread.sleep(1); },后台守护线程即可 - 注意 GC 或调度延迟可能导致更新不准,若要求严格 1ms 精度,需结合
LockSupport.parkNanos等精细控制
不复杂但容易忽略











