atomicinteger 仅适用于简单增减操作,不支持条件判断或复合逻辑;incrementandget()先加后返,getandincrement()先返后加;get()+set()非原子,需用compareandset()重试;高并发下cas易自旋,推荐longadder;static atomicinteger易误用于多维度计数,且不保障业务语义。

AtomicInteger 能直接替代 synchronized 做计数,但前提是只做简单增减;一旦涉及条件判断、复合逻辑或跨字段更新,它就不再安全。
incrementAndGet() 和 getAndIncrement() 的行为差异必须分清
这两个方法都原子,但返回值和执行时机不同:incrementAndGet() 先加 1 再返回新值,getAndIncrement() 先返回旧值再加 1。选错会影响业务逻辑——比如你在埋点里记录“第 N 次请求”,用错就会把序号记偏。
常见错误是误以为 get() + set() 组合能等价于它们。实际不是:中间可能被其他线程插队修改,导致覆盖写入。下面这段代码就是典型陷阱:
int cur = counter.get(); // 可能读到 100 counter.set(cur + 1); // 但此时别人已把值改成 101,你设回 101 就丢了一次更新
这类组合操作必须用原子方法重写,比如用 compareAndSet(expected, updated) 自己实现重试循环。
高并发下 CAS 失败重试会吃 CPU,别盲目迷信“无锁一定快”
当线程数远超 CPU 核心数,或每微秒都在调用 incrementAndGet(),CAS 碰撞概率急剧上升,大量线程陷入自旋重试,反而拖慢整体吞吐。这不是 bug,是硬件层面对乐观策略的自然限制。
- 实测中,单机 QPS 超过 50 万后,
AtomicInteger的性能拐点常出现在 8–16 线程争用同一实例时 - JDK 8+ 推荐改用
LongAdder:它内部拆成多个槽位累加,冲突下降一个数量级,sum()时才合并,适合纯计数场景 - 如果必须用
AtomicInteger,避免把它放在高频 hot path 里反复调用,比如不要在 for 循环内每轮都get()判断阈值
Spring Boot 里声明为 static 字段容易踩的隔离坑
很多人在 Controller 或 Service 里写 private static final AtomicInteger reqCount = new AtomicInteger(0);,本意是统计全站请求数——这没错,但要注意它根本不是“每个用户”或“每个接口”的计数器。
真正容易出问题的是两类误用:
- 想按用户维度计数,却共享了同一个
AtomicInteger实例 → 应该用ThreadLocal<atomicinteger></atomicinteger>或存到 request attribute - 在 @Configuration 类中初始化并注入为 Bean 属性,结果多个 Bean 实例共用一个计数器 → 必须确认作用域(
@Scope("prototype")或明确 new 实例) - 把
get()结果缓存起来做后续判断(如 if (count.get() > 100)),这个值拿到手就可能已过期 → 改用compareAndSet()做原子条件更新
最常被忽略的一点:AtomicInteger 只保整型变量本身的原子性,不保业务语义。比如“库存扣减后不能为负”,它没法帮你做校验;这种逻辑必须外包给更高层的协调机制,比如数据库行锁或 Redis Lua 脚本。










