CAS(CompareAndSwap)原理

CAS是什么

Compare And Swap 比较并交换。CAS是一个cpu原语,该原子性操作不可被中断。

CAS的全称为Compare-And-Swap,它是一条CPU并发原语。

它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。

CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。(原子性)

在这里插入图片描述

CAS原理

下面我们举一个例子来演示CAS原理
atomiclnteger.getAndIncrement();
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

相关参数介绍

1 Unsafe

Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存。

Unsafe类中的方法主要是直接调用操作系统底层资源执行相应任务。

2 变量valueOffset
表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。

3 变量value
用volatile修饰,保证了多线程之间的内存可见性。

UnSafe.getAndAddInt()源码解释:
在这里插入图片描述

var1 AtomicInteger对象本身。
var2 该对象值得引用地址。
var4 需要变动的数量。
var5是用过var1,var2找出的主内存中真实的值。

用该对象当前的值与var5比较:
如果相同,更新var5+var4并且返回true,
如果不同,继续取值然后再比较,直到更新完成。(非常占用cpu)

举例:
假设线程A和线程B两个线程同时执行getAndAddInt操作(分别跑在不同CPU上) :

Atomiclnteger里面的value原始值为3,即主内存中Atomiclnteger的value为3,根据JMM模型,线程A和线程B各自持有一份值为3的value的副本分别到各自的工作内存。

线程A通过getIntVolatile(var1, var2)拿到value值3,这时线程A被挂起。

线程B也通过getintVolatile(var1, var2)方法获取到value值3,此时刚好线程B没有被挂起并执行**compareAndSwapInt方法(CAS原语)**比较内存值也为3,成功修改内存值为4,线程B打完收工,一切OK。

这时线程A恢复,执行compareAndSwapInt方法比较,发现自己手里的值数字3和主内存的值数字4不一致,说明该值己经被其它线程抢先一步修改过了,那A线程本次修改失败,只能重新读取重新来一遍了。

线程A重新获取value值,因为变量value被volatile修饰,所以其它线程对它的修改,线程A总是能够看到,线程A继续执行compareAndSwaplnt进行比较替换,直到成功。

底层汇编

Unsafe类中的compareAndSwapInt,是一个本地方法,该方法的实现位于unsafe.cpp中。

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)
UnsafeWrapper("Unsafe_CompareAndSwaplnt");
oop p = JNlHandles::resolve(obj);
jint* addr = (jint *)index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e))== e;
UNSAFE_END
//先想办法拿到变量value在内存中的地址。
//通过Atomic::cmpxchg实现比较替换,其中参数x是即将更新的值,参数e是原内存的值。

CAS缺点

1、占用过多CPU(循环取值比较)

// ursafe.getAndAddInt
public final int getAndAddInt(Object var1, long var2, int var4){
	int var5;
	do {
		var5 = this.getIntVolatile(var1, var2);
	}while(!this.compareAndSwapInt(varl, var2, var5,var5 + var4));
    return var5;
}

当我们的预期值,与valueOffSet实际指向的值不同时,进行do-while循环,直到 预期值和内存中实际值 相同

2、CAS只能对一个变量进行原子性操作,而synchronized可以对一段代码进行锁操作

3、CAS存在ABA问题
存在两个线程t1,t2,同时访问共享变量A,将共享变量拷贝到线程自己的工作空间。由于t2处理速度快,可能直接修改A为B,再修改B为A。此时,如果t1要修改变量A,对变量A进行compareAndSwap。发现是A没变,直接修改。但是A其实已经被动过,A-B-A的过程可以发生很多事,A已经不再是原来那个单纯的A了

ABA问题的产生

存在两个线程t1,t2,同时访问共享变量A,将共享变量拷贝到线程自己的工作空间。由于t2处理速度快,可能直接修改A为B,再修改B为A。此时,如果t1要修改变量A,对变量A进行compareAndSwap。发现是A没变,直接修改。但是A其实已经被动过,A-B-A的过程可以发生很多事,A已经不再是原来那个单纯的A了

AtomicReference原子引用

之前我们了解过AtomicInteger是对Integer类型的共享变量,要保证数据一致性(原子性)问题时,采取的方案。
如果说我们需要将引用类型作为共享变量,保证原子性。可以使用AtomicReference。

class Person{

    String name;
    Integer age;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}

public class CASTest {

    public static void main(String[] args) {

        Person p1 = new Person("小明", 15);
        Person p2 = new Person("小红", 16);
        Person p3 = new Person("小张", 18);
        // 共享资源p1
        AtomicReference<Person> personAtomicReference = new AtomicReference<>(p1);

        System.out.println("修改成功否" + personAtomicReference.compareAndSet(p1, p2));
        System.out.println("修改成功否" + personAtomicReference.compareAndSet(p1, p3));
    }

}

结果:
在这里插入图片描述

ABA问题解决(AtomicStampedReference)

 public static void main(String[] args) {




        AtomicStampedReference<String> stampedReference = new AtomicStampedReference<>("A",1);
        AtomicReference<String> stringAtomicReference = new AtomicReference<>("A");

        System.out.println("----------ABA 问题产生-----------");

        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + " 初始共享变量值为:"+stringAtomicReference.get());
            stringAtomicReference.compareAndSet("A","B");
            System.out.println(Thread.currentThread().getName() + " 共享变量值为:"+stringAtomicReference.get());
            stringAtomicReference.compareAndSet("B","A");
            System.out.println(Thread.currentThread().getName() + " 共享变量值为:"+stringAtomicReference.get());
        },"t1").start();

        new Thread(()->{
            // 睡眠3秒确保 ,出现ABA问题
            try { TimeUnit.MICROSECONDS.sleep(800); } catch (InterruptedException e) { e.printStackTrace(); }
            stringAtomicReference.compareAndSet("A","C");
            System.out.println(Thread.currentThread().getName() + " 共享变量值为:"+stringAtomicReference.get());
        },"t2").start();



        System.out.println("----------ABA 问题解决-----------");

        new Thread(()->{
            int stamp =  stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + " 当前的版本号为: "+ stampedReference.getStamp());
            // 睡眠一秒确保拿到初始值版本
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            stampedReference.compareAndSet("A","B",stamp,stamp+1);
            System.out.println(Thread.currentThread().getName() + " 当前的版本号为: "+ stampedReference.getStamp() +" 共享变量值为:"+stampedReference.getReference());
            stampedReference.compareAndSet("B","A",stampedReference.getStamp(),stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName() + " 当前的版本号为: "+ stampedReference.getStamp() +" 共享变量值为:"+stampedReference.getReference());
        },"t3").start();

        new Thread(()->{
            int stamp =  stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + " 当前的版本号为: "+stamp);
            // 睡眠3秒确保 ,出现ABA问题,获取内存变量的版本号
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            boolean result = stampedReference.compareAndSet("A", "C", stamp, stamp+1);
            System.out.println("修改成功否 "+result);
            System.out.println(Thread.currentThread().getName() + " 当前的版本号为: "+
                    stampedReference.getStamp() +
                    " 我的版本号为: "+ stamp +
                    " 共享变量值为:"+stampedReference.getReference());
        },"t4").start();

    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

白鸽呀

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

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

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

打赏作者

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

抵扣说明:

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

余额充值