登录
首页 >  文章 >  java教程

currentTimeMillis+原子计数器实现限流方法

时间:2026-05-29 11:09:45 330浏览 收藏

本文深入探讨了如何利用System.currentTimeMillis()与原子计数器(如AtomicInteger)协同实现高效、可靠的单机限流,核心在于通过时间戳对齐固定窗口、借助原子操作保障并发安全计数,并精细把控阈值判断时机(getAndIncrement或incrementAndGet)、惰性清理过期窗口以避免内存膨胀,同时直面固定窗口固有的“双倍突刺”问题,提出缩窗、滑动窗口或令牌桶等进阶方案,为高并发场景下的精准限流提供了兼具实用性与深度的技术实践指南。

如何用currentTimeMillis配合原子计数器做单机限流

System.currentTimeMillis() 配合原子计数器做单机限流,核心是靠时间戳判断是否进入新窗口,并用 AtomicIntegerAtomicLong 安全累加请求数。关键不在“能不能用”,而在于“怎么用才不漏、不超、不卡死”。

用时间戳划分固定窗口

把每秒(或每 N 毫秒)看作一个独立计数周期。每次请求来时,先算出当前所属的时间窗口起点,比如:

  • 按秒切分:窗口 key = System.currentTimeMillis() / 1000
  • 按 500ms 切分:窗口 key = System.currentTimeMillis() / 500

这个 key 决定了该请求归入哪个计数桶。注意:除法结果是向下取整,天然对齐窗口边界。

用原子变量安全计数

不能用普通 int++,因为读-改-写三步非原子,高并发下会丢计数。必须用:

  • AtomicInteger.incrementAndGet() —— 先加再返回,适合“加完立刻判断”场景
  • AtomicInteger.getAndIncrement() —— 先返回再加,适合“加前判断阈值”逻辑

例如:if (counter.getAndIncrement() 表示“如果加之前还没到上限,就放行”。

清理过期窗口要克制

窗口只保留最近几个(如当前 + 前 1 秒),老的 key 要删,否则 map 无限膨胀。但别每次请求都遍历清理:

  • 推荐惰性清理:在插入新 key 前,顺手删掉 key < nowSecond - 1 的旧项
  • 或用定时任务(如 ScheduledExecutorService)每秒扫一次,避免请求线程阻塞
  • 绝对不要在 tryAcquire() 里调 map.keySet().removeIf(...) —— 这会锁整个 map,拖慢所有请求

警惕窗口切换的临界问题

固定窗口天然存在“双倍突刺”风险:比如 0.9s 到 1.0s 放行了 100 次,1.0s 瞬间重置,1.0s 到 1.1s 又放行 100 次 → 实际 0.2s 内来了 200 次。这不是 bug,是算法特性。

若业务敏感(如支付、库存扣减),就得升级方案:

  • 改用滑动窗口(维护多个子窗口,按时间加权)
  • 或直接上令牌桶(允许平滑突发,更适合真实流量)
  • 单机场景下,也可折中:把窗口缩到 200ms,用 5 个桶滚动计数,精度提升,开销可控

今天关于《currentTimeMillis+原子计数器实现限流方法》的内容介绍就到此结束,如果有什么疑问或者建议,可以在golang学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!

资料下载
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>