「并发」浅谈CAS

100 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情

什么是CAS

CAS全称Compare And Swap(比较和交换),乐观锁的一种实现,无锁算法。无锁的情况下实现多线程之间的变量同步。

为什么要有这种东西?

我也好恨啊,为什么有这么多晦涩难懂的东西,学的烦死了烦死了

为了无锁的访问共享资源而不出现问题。

那么为什么要无锁呢?为了免除加锁使用操作系统的mutex这种操作,避免内核态和用户态的频繁切换带来了效率的损耗

CAS算法原理

涉及三个操作数

  • 需要读写的内存值V
  • 用来进行比较的A
  • 需要写入的新值B
讲个故事:小红有三个追求者小甲、小乙、小丙
​
小红发了个朋友圈,“今天看上了一款丝袜叫做巴黎世家”
​
小甲、小乙、小丙第二天都拿着买好的礼物去登门拜访。结果小甲先到就带走了小红共进晚餐。然后小乙、小丙拿着巴黎世家去找小红,小红说我现在已经不喜欢这个了,我喜欢前男友面膜,于是乎小乙、小丙买到了前男友面膜又去找小红。小乙先到了共进晚餐。小丙拿着前男友面膜去找小红,小红说我现在不想要了我想要迪奥999
​
这里的小红是一个共享资源
小甲、小乙、小丙是三个线程
小红想要的:巴黎世家、前男友面膜、迪奥999是需要读写的内存值V
​
小甲、小乙、小丙他们拿的礼物是用来进行比较的A
​
需要写入的新值B是小红的下一个想法

第一步:进入一个CAS循环

第二步:判断A是否等于V。等于则B替换V。不等于则将V赋值给A

第三步:再进入一个CAS循环

1. V=10,线程1想要去对V进行自增1,所以线程1的A=10
2. 线程2已经进行了自增,此时V=11
3. 线程1发现,A!=V。所以将V赋值给A,则A=11
4. 线程1再进行判断交换
  
//含义就是下面的代码(啥也不是的代码)这个代码是原子的
public JNI Integer cas(int A,int B){
  if(A=v){
    v1=v;
    v=B;
    return v1;
  }else{
    A=V;
    return false;
  }
}

当且仅当V=A时(比较),将B替换V(替换)。这个操作在Java的底层借助于一个CPU指令完成的。属于原子操作,所以不会出现比较了还没更新另一个更新的情况。在更新的过程中可能会出现长时间的自旋来等待结果

CAS的三大问题

1、ABA问题:就是内存中的值从A->B->A这个时候另一个线程就以为这个A是没有发生变化的,就可能会造成业务性的错误。解决办法就是增加版本号的概念 1A->2B->3A

2、循环时间长开销大:多个线程进行修改,可能会导致线程之间反复更新一直不成功

3、只能保证一个共享变量的原子操作:Java本身支持的只针对一个变量的修改可以进行CAS操作。不支持多个变量或者多个对象等的修改。(JDK1.5AtomicReference类可以保证对象之间原子性从而将多个变量放到对象中)

CAS的原子性

依赖于底层的操作系统实现。也就是每种操作系统都有一个原语对应着CAS的操作。比如X86架构的cmpxchg。

CAS的实现AtomicInteger

可以做到并发的情况下进行自增线程之间进行信息同步

    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 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));
​
        return var5;
    }
​
    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
​
do-while就是自旋
compareAndSwapInt就是CAS的原子操作
native就是本地方法C++实现
​