CAS

什么是CAS

比较并交换
线程从主物理内存中拿到原始的值,经过一系列操作在准备将修改后的数据重新写回到主物理内存之前,先拿到主物理内存的真实值和期望值进行比较,如果数据没有被其他的线程修改,就可以修改数据并写回到主物理内存中,否则放弃当前的写操作,重新从主物理内存中拿到新的值重新进行比较交换操作

//如果期望值和初始值相等则修改成功,如果不相等则修改失败

//初始值是2
AtomicInteger atomicInteger = new AtomicInteger(2);
//期望值是2,修改值为3
boolean b = atomicInteger.compareAndSet(2, 3);
// true
System.out.println(b);
// 3
System.out.println(atomicInteger.get());

为什么不用synchronized用CAS

  1. 自旋锁 2:Unsafe 类

为什么AtomicInteger能解决线程安全问题

static {
    try {
        //value属性对应内存偏移量
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}
//volatile修饰保证可见性
private volatile int value;
//Unsafe类获取当前对象的内存偏移量的地址
public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}
//然后进行加1的操作
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        //将主内存中的值读取到自己的工作内存中(读取当前对象var1的内存地址偏移量为var2的值)
        var5 = this.getIntVolatile(var1, var2);
        //核心
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
}
//native方法
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

this: 当前对象

valueOffset: 内存偏移量(内存地址)

CAS compare and swap 是一条CPU并发原语,原语的执行必须是连续的,在执行过程中不允许被中断,也就是CAS是一条CPU原子指令,不会造成所谓的数据不一致的问题

CAS的缺点

  1. 需要多次比较,循环时间长,可能会给CPU带来很大的开销
  2. 只能对一个共享变量,保证原子操作,对于多个共享变量操作时,CAS无法保证操作的原子性
  3. 存在ABA的问题

ABA问题

数据中间有被修改,只管结果不管过程

static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
public static void main(String[] args) {
    new Thread(()->{
        atomicReference.compareAndSet(100, 101);
        atomicReference.compareAndSet(101, 100);
    },"t1").start();

    new Thread(()->{
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //修改成功,但是中间有数据变动
        atomicReference.compareAndSet(100, 2019);
        System.out.println(atomicReference.get());
    },"t2").start();
}

原子引用

//原子引用类
AtomicReference<User> atomicReference = new AtomicReference<User>();
User user1 = new User("zhangsan",10);
User user2 = new User("lisi",10);
atomicReference.set(user1);
boolean b = atomicReference.compareAndSet(user1, user2);
System.out.println(atomicReference.get().toString());

如何解决ABA问题

加版本号

//带版本号的原子引用类,初始版本号为1
static AtomicStampedReference stampedReference = new AtomicStampedReference(100, 1);

public static void main(String[] args) {
    new Thread(()->{
        //保证t2拿到第一个版本号
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //将数据从100修改到101,版本号加1
        stampedReference.compareAndSet(100, 101, stampedReference.getStamp(), stampedReference.getStamp()+1);
        System.out.println("版本号为"+stampedReference.getStamp());
        //将数据从101修改到100,版本号继续加1
        stampedReference.compareAndSet(101, 100, stampedReference.getStamp(), stampedReference.getStamp()+1);
        System.out.println("版本号为"+stampedReference.getStamp());
    },"t1").start();

    new Thread(()->{
        int stamp = stampedReference.getStamp();
        //保证t1完成一次ABA操作
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //修改失败,版本号不一致
        stampedReference.compareAndSet(100, 2019, stamp, stampedReference.getStamp()+1);
        System.out.println("版本号为"+stampedReference.getStamp());
        //输出的值还是100
        System.out.println(stampedReference.getReference());
    },"t2").start();
}
posted @ 2021-09-29 17:12  紫川先生  阅读(88)  评论(0编辑  收藏  举报