CAS和Atomic

本文介绍了CAS和Atomic的概念,对比了悲观锁和乐观锁。CAS作为乐观锁的一种实现方式,用于在多线程环境中无锁地更新数据。然而,CAS存在ABA问题,可以通过版本号来解决。Atomic类提供了原子操作,如AtomicInteger,确保在无锁环境下的数据同步。文章还讨论了CAS的循环开销和只能保证单个变量原子性的限制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CAS和Atomic

在了解CAS和Atomic之前我们要先来认识一下悲观锁和乐观锁的一个概念!

悲观锁和乐观锁

悲观锁

假设是每次获取数据都认为会被其他线程修改,每次来操作数据(可读、可写)的时候都会加锁
悲观锁的实现是Synchronized

悲观锁存在问题:

1、多线程竞争,引起性能问题 (锁的释放和获取需要上下文切换和调度等)

2、一个线程获取锁会导致其他线程被挂起(则塞)

乐观锁

假设对数据的操作一般都不会发生冲突,读取操作时,不会加锁,在对数据进行变更操作是,才来检测当前的数据是否发送冲突,发生冲突返回错误信息,让用户类决定如何做

乐观锁阐述的思路,主要有两个步骤操作:冲突检测和数据更新,该方式的实现就是CAS是乐观锁

乐观锁、悲观锁是一种思想,CAS是乐观锁这种思想的实现方法

CAS

CAS的英文为Compare and Swap 翻译为比较并交换多个线程通过CAS尝试修改同一个变量,只有一个线程在同一时刻进行修改,而其他的操作失败,失败的线程不会挂起,告诉失败的线程可以再次尝试:
CAS操作涉及到三个操作数:
需要读写的内存位置(V)、进行比较的预期的原值(A)、待写入的新值(B)

在这里插入图片描述
如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B。否则处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“ 我认为位置 V 应该包含值A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。

CAS引起ABA的问题

假设存在以下线程操作序列:
1、线程1从内存位置V中取得A
2、线程2从内存位置V获取A
3、线程2进行一些操作,将A修改为其他的结果,将B修改为A
4、线程2将A再次写入到位置V
5、线程1进行CAS操作,发现位置V中任然是A,直接修改为B,操作成功
6、尽管线程1操作成功,但并不该表该过程没有问题,对于线程1而言,线程2 的修改以导致数据丢失

举例说明:一个链表ABA的例子:
在这里插入图片描述
1、现有一个单向链表实现的堆栈,栈顶为A,线程1获取到A.next为B,线程1希望通过CAS操作将栈顶替换为B

2、在线程1执行CAS操作之前,线程2来执行,将A、B出站,在依次入栈D、C、A,而对象B处于游离状态

3、此时线程1执行CAS操作,检测栈顶为A,CAS成功执行,栈顶为B,实际是B.next = null,此时堆栈只有一个B,C和D组成的链表不在堆栈中,C\D 被丢弃了

ABA问题的解决方案

ABA问题的解决需要使用版本号,在变量前加上版本号,每次变量的变更操作版本号+1,那么A-B-A就变成1A-2B-3A

使用CAS会引发的问题

CAS虽然比Java中提供的锁的开销小,但是存在问题
1、ABA问题
ABA问题通过版本号解决
2、循环时间长开销大
自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
3、只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

Atomic

Java 中Atomic操作,比如AtomicInteger等原子操作,提供了i++等原子操作

如果读取操作,等将value声明为volatile的,保证在没锁的情况下,数据是同步的

private volatile int value;
public final int get() {
        return value;
    }

涉及到数据变更:AtomicInteger的++i操作:

public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
    }

在这里采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。而这里的comparAndSet(current,next)就是前面介绍CAS的时候所说的依赖JNI实现的乐观锁做法:

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

除了基本数据类型的原子操作类,JUC还提供了一下的Atomic类
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值