CAS比synchronized快是因为不依赖操作系统锁,避免线程挂起/唤醒开销,竞争不激烈时在用户态完成原子操作;但高竞争下反复重试会导致CPU空转,适合读多写少场景。

为什么CAS比synchronized快
CAS(Compare-And-Swap)不依赖操作系统锁,避免了线程挂起/唤醒开销。当竞争不激烈时,它在用户态完成原子操作,而 synchronized 在重量级锁阶段会进入内核态,触发上下文切换。
但要注意:CAS不是万能加速器。高竞争下反复重试会导致CPU空转,反而比阻塞更耗资源。
- 适合读多写少、冲突概率低的场景,比如计数器、状态标记位
- 不适合长临界区或需多个变量协同更新的逻辑(CAS本身只保证单变量原子性)
- JVM对
Unsafe.compareAndSwapInt等有特殊优化,但直接调用Unsafe不安全且JDK9+受限,应优先用AtomicInteger等封装类
AtomicInteger.incrementAndGet()底层做了什么
它本质是循环调用 Unsafe.compareAndSwapInt,直到成功为止。每次失败都重新读取当前值,再尝试更新,属于乐观锁思想。
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
// getAndAddInt 内部类似:
do {
int current = getVolatileValue(); // volatile读
} while (!compareAndSwapInt(current, current + 1));
关键点:
立即学习“Java免费学习笔记(深入)”;
- 必须配合
volatile变量语义,否则其他线程看不到最新值,导致无限重试 -
valueOffset是字段在对象内存中的偏移量,由Unsafe.objectFieldOffset计算,不可手动构造 - 返回值是更新后的值,不是旧值——这点和
getAndIncrement()不同
CAS的ABA问题怎么破
线程A读到值为A,被挂起;线程B把A→B→A改回去;线程A恢复后CAS成功,但中间状态已丢失。典型场景是对象池、栈顶指针更新。
Java提供 AtomicStampedReference 和 AtomicMarkableReference 解决:
-
AtomicStampedReference给每次修改附加一个版本号(int stamp),CAS变成“值+版本”双校验 - 注意:stamp 不能简单自增,要防溢出,建议用
new AtomicInteger()控制 - 不是所有场景都需要——如果业务只关心最终状态,不依赖中间过程,ABA可忽略
自旋重试太多时该不该主动yield或park
标准库的 AtomicXxx 类不做任何让步,纯自旋。是否干预取决于你的使用上下文:
- 在响应敏感型服务(如RPC网关)中,长时间自旋可能拖慢整体吞吐,可考虑用
LockSupport.parkNanos(1)短暂让出CPU - 但加sleep/yield会引入调度不确定性,反而破坏无锁设计初衷
- 更稳妥的做法是换结构:比如用
LongAdder替代AtomicLong,它通过分段热点降低CAS冲突
真正难处理的是「伪共享」(false sharing):多个原子变量在同一CPU缓存行,彼此修改引发缓存同步风暴。这时候加 @sun.misc.Contended 或手动填充字节才有效,光靠CAS解决不了。











