深入理解CAS

CAS(compare-and-swap) ,保证数据的原子性,是硬件对于并发操作共享数据的支持
CAS不同于Synchronized,Synchronized是悲观锁,这种线程一旦得到锁,其他需要锁的线程就挂起;而CAS操作的就是乐观锁,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
当且仅当V==A时,V = B,否则,不进行任何操作
在这里插入图片描述
看看它的源码

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

Unsafe

Unsafe 类方法都是本地方法,因为Java无法直接和操作系统打交道,但是C++可以,所以提供了这个通道来间接操作

	public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

    public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

    public native Object getObjectVolatile(Object var1, long var2);

    public native void putObjectVolatile(Object var1, long var2, Object var4);

    public native int getIntVolatile(Object var1, long var2);

再看看 getAndIncrement 操作,类似Java的 i++
有三个参数,第一个是当前对象
第二个是值的偏移量,可以看成是值的内存地址
第三个就是自增1

 public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);  //取出指定内存地址的值
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); //相等,内存地址值加1

        return var5;
    }

因为cas是直接操作内存,所以效率极高

CAS的缺陷

1.著名的 ABA 问题
2. 只能保证一个共享变量的原子操作
3. 循环时间长开销大

1、ABA 问题
CAS算法实现的一个重要前提是:取出内存中某时刻的数据,而在下一时刻比较并替换,那么在这个时间差中会导致数据的变化。

简单举例:
线程 1 从内存位置V中取出A。
线程 2 从位置V中取出A。
线程 2 进行了一些操作,将B写入位置V。
线程 2 将A再次写入位置V。
线程 1 进行CAS操作,发现位置V中仍然是A,操作成功。
尽管线程 1 的CAS操作成功,但不代表这个过程没有问题——对于线程 1 ,线程 2 的修改已经丢失。

从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

public boolean compareAndSet(
               V      expectedReference,//预期引用
               V      newReference,//更新后的引用
              int    expectedStamp, //预期标志
              int    newStamp //更新后的标志
)
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

public class Test06 {
    public static void main(String[] args) {

        //原子引用,带 版本号的原子操作 乐观锁
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();//得到当前的版本号
            System.out.println("A -version "+stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            boolean compareAndSet = atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println("A IS "+compareAndSet);
            System.out.println("A -version "+atomicStampedReference.getStamp());

            compareAndSet = atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println("A IS "+compareAndSet);
            System.out.println("A -version "+atomicStampedReference.getStamp());


        },"a").start();

        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();//得到当前的版本号
            System.out.println("B -version "+stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean compareAndSet = atomicStampedReference.compareAndSet(1, 7, stamp, stamp + 1);
            System.out.println("B IS "+compareAndSet);
            System.out.println("B -version "+atomicStampedReference.getStamp());
        },"b").start();
    }
}

2、只能保证一个共享变量的原子操作

当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

3、循环时间长开销大

自旋锁,自旋CAS(不成功,就一直循环执行,直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。

        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2); 
            //比较当前工作内存中的值和主内存中的值,如果这个值是期望的,就执行操做,如果不是,就一直循环
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); 

        return var5;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罗罗的1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值