学习一下CAS

场景回顾


去年我写过一篇处理系统中一个并发引起的bug提到了在高并发下如何避免数据状态不一致的问题,里面的解决方案其实已经用到了CAS的思想了。具体的场景请查看文章。


传统的锁


多线程并发修改一份共享数据的时候,通常都是使用加锁的办法,来保证共享数据的正确性和安全性。但是使用锁是有代价的。当一个线程占有了一份共享资源的锁后,其他的线程会被阻塞住,只能等待,啥事情没法干,万一拥有锁的那个线程正在等待一个I/O操作的处理结果,那么其他线程可能需要等待更长的时间。JVM实现阻塞的方式就是挂起线程,之后重新调度被挂起的线程,这样就造成了上下文切换,CPU的吞吐量就大大的降低了。线程应该是要干活的,而不是一直在等待工作或者等待别的线程释放锁


无锁


就像之前在处理系统中一个并发引起的bug一文提到的,不使用任何锁,同样可以在高并发的情况下,保证数据的正确性。这里其实就是CAS思想的体现了。

CAS

CAS的想法很简单,它包含3个参数CAS(V,E,N)

V:将要被更新的变量的值
E : 期望的值
N : 新值

只有当V=E的时候,才将N的值赋给V,否则说明其他线程已经修改过V的值了,已经捷足先登了,当前线程无需再做任何操作,直接退出返回当前V的值。

由于没有使用到锁,同时修改数据失败的线程也没有被挂起,以致变成等待状态。

CAS硬件指令实现

CAS是属于复合操作,如何保证这种【读(read)-改(update)-写(write)】操作的原子性呢?现在的CPU都有实现CAS指令,通过硬件的方式来保证CAS操作的原子性。

备注:
CPU底层实现其实也是要用到锁的,只不过这种复合操作的原子性由硬件来维持,应用层的代码无需关心而已。

AtomicInteger

AtomicInteger类中就有一个CAS操作:

public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset,expect,update);
}

compareAndSet方法利用JNI调用了操作系统的原生接口来达到修改数据的目的。


CAS的ABA问题


如果数据经历过如下变化:A–>B–A,这个时候,原来的线程看到数据还是A,跟预期的一样,就会直接做修改操作。在数据状态敏感的业务场景中,这样可能导致大故障的。

可以利用乐观锁的想法,加一个版本号来做区分。


CAS优缺点


优点
  • 无锁,就不存在死锁和线程竞争资源时被挂起的问题,提高了CPU的利用率,避免了大量的上下文切换开销
缺点

1、上面提到的ABA问题
2、只能保证操作一个共享变量的原子性,如果操作多个共享变量,则无法保证原子性(此时只能用锁的机制了)


参考文章


  1. 聊聊并发(五)——原子操作的实现原理
  2. 高并发Java(4):无锁
  3. JAVA CAS原理深度分析
  4. 非阻塞算法简介
<think>好的,用户想了解CAS的原理及实现。我需要先回顾一下CAS的基本概念。CAS全称是Compare AndSwap,是一种无锁的原子操作,用于实现多线程环境下的数据同步。根据引用[4],CAS是CPU硬件层面的指令,能保证原子性。用户可能想知道它是如何工作的,以及和悲观锁的区别。 接下来,我需要解释CAS的步骤:比较内存值和预期值,如果相同则更新为新值,否则重试或失败。这里要提到Unsafe类的作用,因为引用[1]指出原子类的基础是CAS,而CAS又依赖Unsafe类。可能需要举例说明,比如AtomicInteger的incrementAndGet方法。然后,ABA问题是一个重要点。引用[2]和[3]都提到了ABA问题,即变量被修改后又恢复原值,导致CAS无法察觉。需要解释ABA带来的风险,以及解决方案,比如AtomicStampedReference使用版本号。还要比较CAS和synchronized的区别。引用[4]提到synchronized是悲观锁,而CAS是乐观锁,适用于读多写少的场景。这里需要说明两者的优缺点,比如CAS避免了线程阻塞,但可能增加CPU开销。 用户可能对实现原理感兴趣,需要深入底层,比如CPU的cmpxchg指令,以及Java中如何通过Unsafe类调用本地方法。引用[4]和[5]可能提供这方面的信息。最后,生成相关问题,帮助用户进一步学习。例如,如何解决ABA问题,CAS的优缺点,与锁机制的比较等。确保问题覆盖用户可能感兴趣的扩展方向。</think>### CAS原理及实现讲解 #### 1. **CAS基本概念** CAS(Compare And Swap)是一种无锁的原子操作,用于实现多线程环境下的数据同步。其核心思想是: - **比较**:读取内存位置的当前值,与预期值(旧值)比较 - **交换**:若当前值与预期值一致,则将新值写入该内存位置 - **原子性**:整个过程由CPU硬件指令(如`cmpxchg`)保证不可分割性[^4]。 #### 2. **CAS实现步骤** 以修改共享变量`V`为例: 1. 从内存中读取当前值`oldValue` 2. 计算新值`newValue` 3. 调用CAS指令:若此时内存中`V`的值仍为`oldValue`,则更新为`newValue`;否则重试或放弃[^4]。 #### 3. **Java中的CAS实现** Java通过`Unsafe`类提供底层CAS操作(如`compareAndSwapInt`方法),原子类(如`AtomicInteger`)基于此实现。 例如`AtomicInteger.incrementAndGet()`的伪代码: ```java do { int oldValue = get(); int newValue = oldValue + 1; } while (!compareAndSet(oldValue, newValue)); ``` #### 4. **CAS的三大问题** - **ABA问题** 变量值从A→B→A,CAS无法感知中间变化。解决方案: - 使用版本号标记状态(如`AtomicStampedReference`)[^2][^3] - **自旋开销** 竞争激烈时可能导致长时间循环。优化方法: - 限制自旋次数 - 结合锁机制(如`LongAdder`的分段锁) - **单变量原子性局限** CAS只能保证单一变量的原子操作,多变量需结合其他同步机制[^4]。 #### 5. **CAS与锁的对比** | 特性 | CAS(乐观锁) | Synchronized(悲观锁) | |--------------------|-----------------------|------------------------| | 线程阻塞 | 无自旋消耗CPU | 线程挂起不消耗CPU | | 适用场景 | 低竞争、简单操作 | 高竞争、复杂操作 | | 原子性实现 | CPU硬件指令 | 操作系统互斥量 | #### 6. **实际应用案例** - 并发计数器(如`AtomicInteger`) - 无锁队列(如`ConcurrentLinkedQueue`) - 状态标志位更新(如线程池状态控制)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值