atomicinteger的incrementandget比synchronized快,因其基于cas指令,无阻塞、无上下文切换;但高争用时可能因重试过多反不如锁,且仅适用于简单原子操作。

AtomicInteger 的 incrementAndGet 为什么比 synchronized 快
因为 incrementAndGet 底层调用的是 CPU 的 CAS(Compare-And-Swap)指令,不阻塞线程、不进锁队列,避免了上下文切换和锁竞争开销。而 synchronized 在高并发下容易触发偏向锁→轻量级锁→重量级锁升级,一旦膨胀成系统互斥锁,性能会断崖式下降。
但要注意:CAS 不是万能的。在极端高争用场景(比如上千线程反复抢同一个 AtomicInteger),失败重试次数激增,反而可能比锁更耗 CPU。
- 适用场景:计数器、序列号生成、统计指标(如请求量、错误数)这类“读多写少”或“写操作简单”的场景
- 不适用场景:需要原子性地执行多个变量联动更新(例如“余额减 100 同时记录流水号”),这时得用
Lock或事务机制 -
incrementAndGet()返回新值,getAndIncrement()返回旧值——别用反,尤其在条件判断里
compareAndSet 是唯一能做条件更新的原子操作
如果要实现“仅当当前值为 X 时才加 1”,必须用 compareAndSet,而不是先 get() 再 set() ——后者中间有竞态窗口,不是原子的。
典型错误写法:
int cur = counter.get();
if (cur == 5) {
counter.set(6); // ❌ 可能被其他线程改过 cur 了
}
正确写法:
int expected = 5; boolean updated = counter.compareAndSet(expected, expected + 1); // updated 为 true 表示成功;false 表示期间值已被改,需重试或放弃
-
compareAndSet是乐观策略:假设冲突少,失败再试;适合低到中等争用 - 若业务逻辑复杂、重试代价高,不如直接上
ReentrantLock,别硬套 CAS - 注意参数顺序:
compareAndSet(expectedValue, newValue),别把两个值写反
AtomicInteger 不能替代 long 类型的原子运算
AtomicInteger 只包装 int(32 位),最大值是 Integer.MAX_VALUE(2147483647)。一旦计数超过这个值,incrementAndGet() 会从 Integer.MAX_VALUE 溢出变成 Integer.MIN_VALUE(-2147483648),不是抛异常,而是静默翻转。
如果你的计数器可能超 21 亿(比如日志行数、消息处理量),必须换用:
-
AtomicLong:支持 64 位,上限约 9×10¹⁸,够绝大多数场景 -
LongAdder:在超高并发累加场景下比AtomicLong更快(内部分段累加,减少 CAS 争用),但不支持compareAndSet - 别自己用
synchronized包裹long变量来“模拟”原子性——这等于放弃无锁优势
get() 和 lazySet() 的内存语义差异常被忽略
get() 是 volatile 读,保证看到最新值;lazySet()(即 set() 的弱版本)只保证写入不被重排序,但不保证后续读操作立即看到它——JVM 可能延迟刷回主存。
这意味着:
- 用
lazySet(value)替代set(value)能略微提升写性能(尤其在写密集、读不紧耦合的场景,如信号量清零、状态标记) - 但绝不能用于“写完立刻要读”的逻辑,例如:
counter.lazySet(0); assert counter.get() == 0;这个断言可能失败 - 多数业务代码用不到
lazySet,除非你明确在压测中发现set()成为瓶颈,且能接受最终一致性
无锁不是没有成本,只是把同步成本从阻塞转移到了 CPU 重试和内存屏障上。真正关键的,是看清楚你的计数场景是否真的需要无锁——很多所谓“高并发”系统,其实每秒几百次更新,synchronized 完全够用,还更易懂、更难出错。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











