CAS 为什么效率高?
2026/6/9 1:15:23 网站建设 项目流程

CAS 的高效,核心在于“无锁”和“硬件原子指令”两个层面,避免了传统锁的诸多开销。

  1. 无需操作系统内核态切换(最核心的优势)

    • 传统互斥锁:线程获取锁失败,会进入阻塞状态(如调用pthread_mutex_lock),这需要操作系统介入,完成“用户态 → 内核态 → 用户态”的切换。一次上下文切换的代价可能高达几万到几十万个 CPU 时钟周期。

    • CAS:全部操作都在用户态完成。它只是一条 CPU 原子指令(如 x86 的cmpxchg)。执行时,要么成功,要么失败立即返回,不会主动让线程挂起。这避免了系统调用和上下文切换的昂贵开销,在临界区极短的场景下,性能提升是指数级的。

  2. 乐观的非阻塞策略,延迟极低

    • CAS 的默认态度是“乐观”的:假设没有冲突,直接尝试修改。如果没冲突,指令就瞬间完成,延迟极低。

    • 相比之下,锁是“悲观”的,会先假设有冲突,必须独占。

  3. 细粒度的原子操作

    • 锁保护的是一段代码块,而 CAS 直接保护对一个单个变量的更新。这种极致的细粒度,为 AQS 中statewaitStatus等字段的无锁化操作提供了基础。

在 AQS 中的体现:大量并发线程争用锁时,首先通过 CAS 对state进行“光速”抢占。抢不到才去构建队列,这是高吞吐量的关键。


CAS 有什么缺点吗?

它的优点也是它缺点的根源,主要有三个问题:

1. 经典的 ABA 问题

这是由 CAS “只看表面值”的机制决定的。

  • 问题:线程 T1 读取值 A → 线程 T2 将 A 改为 B 又改回 A → 线程 T1 执行 CAS,发现值还是 A,误以为从未被修改过,从而成功更新。

  • 后果:在某些场景下,这个“没变”隐藏了逻辑错误。比如,用 CAS 管理一个栈顶指针,如果原节点 A 被回收并恰好又作为新节点放回,CAS 会成功,但链表结构可能已错乱。

  • 解决:引入版本号(如 Java 的AtomicStampedReference)。AQS 的statewaitStatus等字段,其业务语义不关心中间历史,只关心当前状态符不符合预期,所以 ABA 问题在 AQS 内部通常无害

2. 循环自旋带来的 CPU 空转开销

这是 CAS 在竞争激烈时暴露的最大缺陷。

  • 问题:CAS 失败后,通常会使用while循环不断重试(即自旋)。如果竞争激烈,大量线程都在疯狂自旋,会导致 CPU 资源被严重浪费,甚至拖慢整个系统。

  • AQS 的智慧:AQS 正是为了解决这个缺点而设计的。它让线程的等待分为两个阶段:

    • 短暂自旋:尝试几次 CAS,适用于锁很快就释放的场景。

    • 挂起等待:如果多次自旋失败,就用LockSupport.park()让线程进入休眠,彻底释放 CPU。当锁被释放时,再由前驱节点精确唤醒。这本质上是用“锁+队列”的机制,来弥补 CAS 在长期等待时的低效。这正是 AQS 对 CAS 缺点的完美修补。

3. 只能保证单个变量的原子性
  • 局限:CAS 指令本身只能对一个内存地址进行原子操作。

  • 后果:如果想让多个共享变量同时原子地更新(比如转帐场景:A 账号减钱,B 账号加钱),用一次 CAS 就无法实现。此时必须借用锁,或者用更复杂的原子类(如AtomicReference)配合不可变对象的设计。

总结一下,CAS 和 AQS 的关系是:
CAS 是无锁原子操作的“快进键”,负责在低竞争时极速通行;而 AQS 则是当“快进键”失效时,自动切换到的“有序排队系统”,负责用挂起/唤醒的方式优雅地管理高并发冲突,并解决 CAS 自身的自旋消耗和功能局限。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询