CAS介绍

1.举例

启动5个线程,每个线程执行1000遍相加操作,若没有并发控制,那么普通自增统计就会有错

@Slf4j
public class CasDemo{
    public static void main(String[] args) throws InterruptedException {
        MyData myData = new MyData();
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    myData.commonAdd();
                    myData.atoAdd();
                }
            }).start();
        }
        //增加等待时间,等待其他子线程执行完后再获取值
        Thread.sleep(5000);
        log.info("普通自增: {}",myData.getN1());
        log.info("原子自增: {}",myData.getN2());

    }
}
@Slf4j
public class MyData {

    private int n1 = 0;
    private AtomicInteger n2 = new AtomicInteger();

    public void commonAdd(){
        n1 = n1 + 1;
//        log.info(String.valueOf(n1));
    }


    public void atoAdd(){
        n2.getAndIncrement();
    }

    public int getN1() {
        return n1;
    }

    public AtomicInteger getN2() {
        return n2;
    }
}

如下,普通自增并没有完全相加得5000,而是4881,说明中间有些值重复了
在这里插入图片描述

2.CAS

2.1 介绍

CAS(Compare And Swap),即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数–内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。

在JAVA中,sun.misc.Unsafe类提供了硬件级别的原子操作来实现这个CAS。 java.util.concurrent 包下的大量类都使用了这个 Unsafe.java 类的CAS操作。至于 Unsafe.java 的具体实现这里就不讨论了。

CAS典型应用
java.util.concurrent.atomic 包下的类大多是使用CAS操作来实现的(eg. AtomicInteger.java,AtomicBoolean,AtomicLong)。

CAS的目的
synchronized是悲观锁,这种线程一旦得到锁,其他需要锁的线程就挂起的情况就是悲观锁。也是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
CAS操作的就是乐观锁,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线程只会收到操作失败的信号。
尽管Java1.6为Synchronized做了优化,增加了从偏向锁到轻量级锁再到重量级锁的过度,但是在最终转变为重量级锁之后,性能仍然较低。

利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。而整个J.U.C都是建立在CAS之上的,因此对于synchronized阻塞算法,J.U.C在性能上有了很大的提升。

2.2 机制

CAS(V, A, B),V为内存地址、A为预期原值,B为新值。如果内存地址的值与预期原值相匹配,那么将该位置值更新为新值。否则,说明已经被其他线程更新,处理器不做任何操作;无论哪种情况,它都会在 CAS 指令之前返回该位置的值。而我们可以使用自旋锁,循环CAS,重新读取该变量再尝试再次修改该变量,也可以放弃操作。

我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。
比较 A 与 V 是否相等。(比较)
如果比较相等,将 B 写入 V。(交换)
返回操作是否成功。

在这里插入图片描述
在这里插入图片描述
如上图,当需要修改内存中一个变量值时,比如线程1从内存中获取了一个值时10,然后赋值给A(预期原值),然后再进行相加,得B(期望新值),这个时候,另外一个线程2也做了这个操作,并提前提交了修改的值11,那么当线程再提交的时候,发现内存中的值是11了,而不是原来的10,那么线程1就提交失败,返回false,然后再重新循环再从内存中获取新修改的值,赋值给A,这个时候B就为11+1,然后再提交,发现原来地址的值还是11,那么就把B的值赋值给V,整个CAS结束

2.3 代码分析

    public void atoAdd(){
        n2.getAndIncrement();
    }
    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));

        return var5;
    }

参数说明:
Object var1 为对象。
long var2 为 var1 对象的内存地址。
var5 为 主内存中真实值
Var5+var4 为 需要修改的增量值

getIntVolatile(var1, var2):这个方法会从主内存里获取该对象的实际值查询出来
compareAndSwapInt(var1, var2, var5, var5 + var4):这个方法会把真实值和当前线程工作区里的该对象的值进行比较,若主内存的值和当前线程期望原值是一样的,那么就用var5 + var4更新主内存的值
在这里插入图片描述

2.4 缺点

CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作

  1. ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。

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

  1. 循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

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

3.其他参考

Java:CAS(乐观锁)
JAVA 中的 CAS
Java 的 CAS原理
JAVA中的CAS

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值