Atomic类通过CAS实现无锁原子性,依赖CPU底层指令保证“读-判-写”不可分割,适用于纯内存简单状态变更;不适用于复合操作、I/O或跨变量约束场景。

Atomic类靠CAS实现无锁原子性,不是靠synchronized或Lock
Atomic类(如AtomicInteger)的线程安全,根本不是加锁来的。它依赖CPU提供的底层原子指令——CAS(Compare-And-Swap),在硬件层面保证“读-判-写”三步不可分割。只要当前值等于预期值,就原子地更新为新值;否则失败并返回false,由调用方决定重试或放弃。
-
compareAndSet(int expect, int update)是核心方法,所有其他操作(如incrementAndGet())最终都基于它循环尝试 - 没有线程阻塞,没有上下文切换开销,高并发下吞吐量明显优于
synchronized块 - 但要注意:CAS成功 ≠ 业务逻辑成功——比如多个线程同时对计数器+1,结果一定正确;但若+1前还要查数据库校验余额,CAS就管不了这层语义了
什么时候该用AtomicInteger而不是int + synchronized?
典型场景是「纯内存状态变更」且操作足够简单:计数器、开关标志、序列号生成、引用计数等。一旦涉及I/O、多变量协同、条件分支依赖共享状态,就该退回到锁或更高级并发结构。
- ✅ 推荐用
AtomicInteger:likeCount.incrementAndGet()统计点赞数 - ✅ 推荐用
AtomicBoolean:running.compareAndSet(true, false)安全关闭轮询线程 - ❌ 别硬套:
if (counter.get() > 100) counter.set(0)—— 这里存在竞态窗口,应改用getAndUpdate()或锁 - ⚠️ 注意初始值:不显式传参时默认为0,但
new AtomicInteger(-1)这种非零初始化很常见,别漏掉
compareAndSet失败后怎么办?别直接抛异常或忽略
compareAndSet()返回false不是异常,而是明确告诉你“值已被别人改了”。这时候不能沉默,也不能粗暴重试无限循环(可能饿死CPU),得结合业务做合理兜底。
- 常见做法是用
getAndUpdate()或updateAndGet()——它们内部已封装了CAS自旋逻辑,语义更清晰 - 若需复杂判断(比如“仅当旧值为X且满足Y条件才更新”),建议用
getAndAccumulate()配合IntBinaryOperator - 极端低延迟场景下,可考虑
lazySet()代替set():它不保证立即可见,但写入更快,适合发布状态标记(如done.lazySet(true)) - 警惕ABA问题:如果一个值从A→B→A,
CAS会误认为没变。JDK提供了AtomicStampedReference带版本戳解决,但多数计数类场景无需考虑
Atomic类不是万能的,别把它当“线程安全银弹”
它只保单个变量的原子读写,不保复合操作、不保可见性传播、不保跨变量约束。比如两个AtomicInteger相加再存入第三个,这个“加+存”整体仍非原子。
- 多个原子变量之间无顺序保证:线程1执行
a.set(1); b.set(2),线程2可能看到a==1 && b==0(因JVM和CPU重排序) - 想让多变量操作强一致?要么上锁,要么用
AtomicReference把多个字段包进一个不可变对象里整体更新 - 局部变量永远线程安全,别为了“看起来统一”而滥用Atomic——
int local = x.get() + y.get();比AtomicInteger local = new AtomicInteger(x.get() + y.get());干净得多
真正容易被忽略的是:Atomic类只解决原子性,不自动解决可见性和有序性边界。哪怕用了AtomicInteger,如果在它之后立刻读一个普通int字段,那个读操作仍可能看不到最新值——该加volatile还得加,该同步代码块还得同步。










