倒计时抢购必须用 system.nanotime() 配合 system.currenttimemillis() 实现可靠计时:前者提供单调递增的纳秒差值,后者记录绝对截止时间;通过 atomiclong 管理剩余毫秒数保障线程安全,每次仅调用一次 nanotime() 计算差值并整数运算格式化。

倒计时抢购这类场景,不能靠 System.currentTimeMillis() 做核心计时——它会被系统时间调整干扰,导致“还没开始就结束”或“已结束还在倒”。真正可靠的做法是用 System.nanoTime() 测相对耗时,再结合一次基准时间换算出可读的剩余秒数。
为什么 nanoTime 是倒计时的唯一合理选择
它不依赖系统墙钟,不受 NTP 同步、手动改时间影响;返回值单调递增,不会跳变或回拨;纳秒级分辨率(实际约 10–15 纳秒)远超毫秒需求。但要注意:它不是时间戳,不能直接格式化成 HH:MM:SS——它的数值本身没有现实时间意义,只适合做差值。
倒计时核心逻辑:基准时间 + 纳秒差值
启动那一刻,必须同时记录两个值:
- 用
System.currentTimeMillis()记下绝对截止时间(比如抢购结束时刻的毫秒时间戳) - 用
System.nanoTime()记下当前纳秒起点(作为计时跑表的“零点”) - 后续每次检查剩余时间:用当前
nanoTime()减去起点,转为毫秒,再从截止时间里减去这个已过毫秒数
线程安全的关键:用 AtomicLong 管理剩余毫秒
抢购服务通常是多线程并发访问的,状态更新必须原子化:
- 别用普通 long 字段加 synchronized —— 锁太重,易阻塞
- 用
AtomicLong存储剩余毫秒数,通过compareAndSet(old, new)更新 - 一旦剩余 ≤ 0,立刻执行
compareAndSet(remaining, 0)锁死状态,防止重复触发结束逻辑 - 所有读取都直接 get(),无锁开销
显示与精度处理:别在循环里反复调用 nanoTime()
UI 或接口返回的倒计时数字(如 “00:02:17”),本质是剩余秒数的格式化结果:
- 每次计算时,只调用一次
System.nanoTime()获取当前值,减去初始值再转毫秒 - 避免在 for 循环里反复调用 nanoTime() 累加——不仅引入浮点误差,还放大调用开销(单次约 10–30 纳秒)
- 剩余毫秒转秒/分/小时时,用整数除法和取余即可,无需浮点运算











