Java的CAS机制:无锁并发控制及其高频面试题

一、什么是CAS?

CAS(Compare-And-Swap,比较并交换)是一种无锁的原子操作,用于实现多线程同步。它是现代CPU通过硬件支持的原子指令,也是许多并发工具类的底层实现原理。

CAS 操作包含三个操作数:

  • 内存位置(V)​
  • 预期原值(A)​
  • 新值(B)​

基本逻辑是:​

如果内存位置 V 的值与预期原值 A 相匹配,那么就将该位置的值更新为新值 B;否则,不修改该值。无论哪种情况,都会返回该内存位置当前的值。

这个操作是原子性的,即执行期间不会被其他线程打断。


二、Java中的CAS实现

在 Java 中,CAS 操作主要通过 java.util.concurrent.atomic包下的原子类实现,比如:

  • AtomicInteger
  • AtomicLong
  • AtomicBoolean
  • AtomicReference

这些类底层使用 ​Unsafe 类​ 调用了 CPU 提供的 CAS 指令(如 x86 架构下的 CMPXCHG指令)。

示例:AtomicInteger 中的 CAS

AtomicInteger atomicInteger = new AtomicInteger(0);

// 模拟 CAS 操作:如果当前值是 0,就设置为 1
boolean success = atomicInteger.compareAndSet(0, 1);
  • compareAndSet(expect, update)方法就是 CAS 的体现
  • 它会检查当前值是否等于 expect,如果是,则更新为 update,并返回 true;否则不更新,返回 false

三、CAS 的工作原理与特点

特点:

  1. 乐观锁策略​:假设并发冲突不常发生,先尝试更新,如果不成功则重试或不处理
  2. 无锁(Lock-Free)​​:不使用传统 synchronized 或 Lock,避免了线程阻塞和上下文切换
  3. 原子性​:CAS 操作本身是原子的,由硬件保证
  4. 自旋(Spin)​​:当 CAS 失败时,通常会采用自旋(循环重试)的方式,直到成功

缺点:

  1. ABA 问题​:一个值从 A 变成 B 又变回 A,CAS 会认为没有变化,但实际上已经变更过
  2. 自旋开销​:在高并发场景下,如果 CAS 长时间不成功,会导致大量 CPU 空转
  3. 只能保证一个变量的原子操作​:无法直接用于多个变量的原子更新

四、CAS 的常见应用

1. 原子类

AtomicInteger counter = new AtomicInteger(0);

// 线程安全的自增
counter.incrementAndGet(); // 内部使用 CAS

2. 实现非阻塞算法

比如实现一个简单的非阻塞栈或队列,利用 CAS 来更新头尾节点。

3. AQS(AbstractQueuedSynchronizer)

Java 中很多同步工具如 ReentrantLock、CountDownLatch、Semaphore 的底层都依赖 AQS,而 AQS 的核心部分就使用了 CAS 来控制线程的排队与唤醒。


五、CAS 高频面试题

1. 什么是 CAS?它的底层原理是什么?

CAS(Compare-And-Swap)是比较并交换的缩写,是一种无锁的原子操作,用于实现多线程同步。它包含三个操作数:内存地址 V、预期原值 A 和新值 B。当且仅当 V 的值等于 A 时,才将 V 的值更新为 B,否则不修改值。整个操作是原子的,由 CPU 硬件指令保证。

在 Java 中,CAS 通过 Unsafe类调用本地方法,最终由 CPU 的原子指令(如 x86 的 CMPXCHG)实现。


2. Java 中哪些类使用了 CAS?

Java 中的 java.util.concurrent.atomic包下的原子类,如:

  • AtomicInteger
  • AtomicLong
  • AtomicBoolean
  • AtomicReference
  • 以及它们的数组版本,如 AtomicIntegerArray

此外,JUC 中的许多同步工具(如 ReentrantLock、CountDownLatch、Semaphore)的底层实现也依赖 CAS。


3. CAS 和 synchronized 有什么区别?各自优缺点是什么?

对比维度

CAS

synchronized

实现机制

无锁,基于硬件原子指令

有锁,基于监视器锁(Monitor)

阻塞性

非阻塞,线程不会挂起

阻塞,未获取锁的线程会被挂起

线程状态

不会进入阻塞态,一直处于就绪或运行

可能进入 BLOCKED 或 WAITING 状态

性能

高并发下可能自旋消耗 CPU

高并发下线程切换开销大

适用场景

低冲突、简单原子操作

复杂同步逻辑、临界区较大

CAS 优点:​​ 无锁、并发性能高、避免线程上下文切换

CAS 缺点:​​ ABA 问题、自旋可能浪费 CPU、只能保证单个变量的原子性


4. 什么是 CAS 的 ABA 问题?如何解决?

ABA 问题:​

线程 1 读取内存值为 A,准备将其更新为 B;但在它执行 CAS 之前,线程 2 将值从 A 改为 B,又改回 A。此时线程 1 执行 CAS,发现当前值仍是 A,于是更新成功,但实际上中间已经发生过变更。

解决方法:​

使用 ​带版本号的原子引用类,如 AtomicStampedReferenceAtomicMarkableReference,在比较值的同时也检查版本号或标记位,从而识别出值是否被更改过。

示例:

AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0);

// 更新时同时检查值和版本号
int stamp = ref.getStamp();
ref.compareAndSet(100, 101, stamp, stamp + 1);

5. CAS 自旋(循环重试)会导致什么问题?如何优化?

问题:​

当多个线程同时尝试修改同一个变量,只有一个线程会成功,其他线程 CAS 失败后会不断自旋重试,如果并发很高或冲突严重,会导致大量 CPU 空转,浪费资源。

优化方式:​

  1. 限制自旋次数,超过阈值后可以放弃或转为阻塞
  2. 退避策略,如随机等待一段时间再重试
  3. 结合锁使用,在竞争激烈时降级为锁机制
  4. 减少热点变量的竞争,优化数据结构和算法

6. 为什么说 CAS 是乐观锁?

因为 CAS ​假设并发冲突不常发生,采取“先尝试更新,失败再处理”的策略,而不是像悲观锁那样“先加锁,再操作”。它不阻止其他线程的并发访问,而是通过原子操作和重试机制来保证正确性,因此属于乐观锁的一种实现方式


7. CAS 能否保证多个变量的原子操作?

不能直接保证。​

CAS 只能针对单个变量的原子更新。如果需要保证多个变量更新的原子性,可以采用以下方式:

  • 使用一个对象封装多个变量,然后对该对象的引用进行 CAS(如 AtomicReference
  • 使用锁(synchronized 或 Lock)
  • 使用 AtomicReference结合自定义对象

六、总结

CAS 是 Java 并发编程中非常重要的基础技术,是实现高性能无锁算法和并发容器的核心。它在 Atomic类、AQS、并发工具类中都有广泛应用。

优点:​​ 无锁、高并发性能好、避免线程阻塞

缺点:​​ 存在 ABA 问题、自旋可能浪费 CPU、只能保证单一变量原子性

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值