System.currentTimeMillis()开销极小,性能瓶颈源于误用:循环调用、锁内高频获取或轮询等待;ScheduledExecutorService的价值在于避免无效时间检查,将主动轮询转为被动调度。

System.currentTimeMillis() 本身开销极小,单次调用通常在纳秒级,高并发下频繁调用并不会成为性能瓶颈。真正的问题往往出在**误用场景**——比如在循环中反复调用、或在锁内高频获取时间戳导致竞争加剧,又或者用它做“轮询等待”引发 CPU 空转。
而 ScheduledExecutorService 并不直接优化 currentTimeMillis() 的调用性能,它的价值在于:帮你**避免高频、无效、阻塞式的时间检查**,把“主动轮询”变成“被动调度”,从而减少无谓调用、降低线程争用、提升响应确定性。
用定时任务替代忙等待(Busy Waiting)
常见反模式:在 while 循环里反复调用 System.currentTimeMillis() 等待某个时间点,例如实现“延时执行”或“超时重试”。
- ❌ 错误写法(浪费 CPU,且精度差):
long deadline = System.currentTimeMillis() + 5000;
while (System.currentTimeMillis() < deadline) {
// 空转或做其他事 —— CPU 占用高,还可能错过时机
}
- ✅ 正确做法:交给
ScheduledExecutorService调度
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.schedule(() -> {
// 到点自动执行,不占 CPU,精度由 OS 和 JVM 共同保障
doSomething();
}, 5, TimeUnit.SECONDS);
批量聚合时间敏感操作,减少调用频次
如果业务逻辑需要为一批任务打上“同一时刻”的时间戳(如日志批次、指标采样),不要每个任务都调一次 currentTimeMillis()。
立即学习“Java免费学习笔记(深入)”;
- 在调度任务中统一获取一次时间戳,再分发给多个子任务
- 既保证时间一致性,又将 N 次调用降为 1 次
- 适合定时上报、缓存刷新、心跳生成等场景
scheduler.scheduleAtFixedRate(() -> {
long now = System.currentTimeMillis(); // ✅ 只调一次
batchProcessTasks(tasks, now);
}, 0, 10, TimeUnit.SECONDS);
避免在 synchronized 或锁区内调用 currentTimeMillis
看似无关的操作,一旦放进锁块,就会延长临界区时间——尤其当锁竞争激烈时,currentTimeMillis() 虽快,但放大了锁持有时间。
- 提前把时间戳算好,再进锁;或改用无锁结构(如
AtomicLong记录上次时间) - 若必须动态判断超时,优先用
Lock.tryLock(timeout, unit)这类带超时的非阻塞方法,而非自己轮询
注意 ScheduledExecutorService 自身的开销与配置
它不是银弹,用不好反而引入新问题:
- 线程池大小要合理:定时任务过多时,
newScheduledThreadPool(1)会串行阻塞;高吞吐场景建议配合ThreadFactory命名和监控 - 任务抛异常会被静默吞掉:务必在
Runnable内加 try-catch + 日志,或包装成Future主动检查 - 慎用
scheduleWithFixedDelay做“精确周期”:它基于上一次结束时间计算下次,实际间隔可能漂移;需严格周期用scheduleAtFixedRate,并确保任务执行远小于周期



















