AtomicInteger.incrementAndGet() 做限流会失效,因其缺乏时间维度约束,无法自动清零或滑动窗口,导致请求集中爆发或窗口撕裂;应改用时间分片(如秒级窗口)配合 ConcurrentHashMap 与 LongAdder 实现精准限流。

为什么直接用 AtomicInteger.incrementAndGet() 做限流会失效
因为限流不是单纯计数,它需要「时间维度」约束。只靠 AtomicInteger 自增,数值永远涨,没法自动清零或滑动——你看到的“每秒最多100次”,实际可能前10毫秒就打满100次,后面990毫秒完全被拦住,体验断崖式下跌。
常见错误现象:AtomicInteger 计数器一路飙升,但限流逻辑没生效;或者手动定时重置(比如用 ScheduledExecutorService 每秒清零),结果在清零瞬间出现窗口撕裂:清零前最后一毫秒+清零后第一毫秒,两波请求叠加突破阈值。
- 别在业务线程里 sleep 等待窗口结束——这等于把非阻塞变成阻塞
- 别用
System.currentTimeMillis()做窗口分界——毫秒级精度在高并发下容易误判同一窗口 - 避免用
new Date()或LocalDateTime.now(),对象创建开销大,且不适用于高频 CAS 场景
用 LongAdder + 时间戳分片替代单个 AtomicInteger
LongAdder 比 AtomicInteger 更适合高并发累加,但它本身也不带时间语义。真正关键的是「把时间切片化」:比如按秒级窗口,就用当前秒数(System.nanoTime() / 1_000_000_000)做 key,每个窗口独立计数。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 用
ConcurrentHashMap<long longadder></long>存储「窗口开始时间 → 当前计数」,key 是窗口起始秒的时间戳(如1717023600L) - 每次请求先算当前窗口:
long window = System.currentTimeMillis() / 1000,再 get or compute if absent - 调用
adder.increment(),然后立即检查adder.sum() ,超了就拒绝 - 不主动清理旧窗口——靠
ConcurrentHashMap的弱引用或定期扫描keySet()过期项(比如只保留最近3个窗口)
compareAndSet() 在限流里基本没用武之地
很多人想用 AtomicInteger.compareAndSet(expected, updated) 实现“仅当未超限时才更新”,但这是错的:CAS 只保证原子性,不保证逻辑正确性。两个线程同时读到 count=99,都判断“99 compareAndSet(99, 100),结果一个成功、一个失败——失败的那个线程已经做了业务判断,却还要兜底处理,反而增加复杂度。
更糟的是,如果阈值是动态的,或涉及多个变量(比如“剩余配额 + 时间戳”联合判断),compareAndSet() 根本无法覆盖。
- 除非你在写底层无锁数据结构,否则对业务限流来说,
incrementAndGet()或add()配合即时判断更直白可靠 -
weakCompareAndSet()和compareAndSet()行为差异在 JDK 9+ 已趋同,不用刻意区分,但都绕不开上面的逻辑缺陷 - 真要强一致性校验,请用
StampedLock或分离读写路径,而不是硬套 CAS
滑动窗口必须自己算偏移,AtomicInteger 不支持
固定窗口简单,但抖动大;滑动窗口平滑,可 Java 标准库没有 AtomicSlidingWindow 这种东西。所谓“滑动”,本质是维护一个数组或环形缓冲区,每个槽位存一个时间片的计数,然后每次请求要累加最近 N 个槽位的和。
这时候 AtomicInteger 单个变量完全不够用,必须用数组+索引控制:
- 定义
AtomicInteger[] slots = new AtomicInteger[windowSize],比如 60 个槽代表 60 秒 - 用
int index = (int)(System.currentTimeMillis() / 1000 % windowSize)定位当前槽 - 先
slots[index].incrementAndGet(),再遍历所有槽 sum —— 但注意:遍历过程非原子,sum 可能不准,只能作为近似参考 - 更稳妥的做法是用
LongAdder[],每个槽独立累加,避免遍历时锁竞争
滑动窗口真正的复杂点不在计数,而在「如何让过期槽位的计数自然归零而不影响正在累加的请求」——这需要配合时间戳标记或延迟回收,不是靠某个函数就能一键解决的。










