CAS原理

简介

CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术。简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替换当前变量的值。

        AtomicInteger atomicInteger = new AtomicInteger(10);
        System.out.println(atomicInteger.compareAndSet(10, 4));
        System.out.println(atomicInteger.compareAndSet(10, 2));
        System.out.println(atomicInteger);

底层原理

以AtomicInteger的getAndIncrement方法为例:

    public final int getAndIncrement() {
        //this:当前对象
        //valueOffset:内存地址偏移量(内存地址)
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

这里由unsafe调用getAndAddInt方法。

Unsafe

Unsafe是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法类访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为java中CAS操作的执行依赖于Unsafe类的方法。

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

image-20210121153356036

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

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

CAS(Compare and swap),是一条CPU并发原语。它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。

原语的执行必须是连续的,执行过程中不允许打断,所以CAS是一条CPU的原子指令,所以不会造成所谓的数据不一致问题。

image-20210121154448868

var1:AtomicInteger对象本身

var2:该对象值的引用地址

var4:需要变动的值

var5:是用过var1 var2找出的主内存中真实的值

compareAndSwapInt:var2和var5比较,相同则 var5+var4,并返回true,跳出do-while循环。如果不同,继续取值再比较,直至更新完成。

CAS缺点

1)如果CAS失败,会一直尝试,如果CAS长时间不成功,可能会给CPU带来很大的开销

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

3)ABA问题

ABA

如果一个线程1从内存位置V中取出A,这时候另一个线程2也从内存中取出A,并且线程2进行了一些操作将值编程了B,然后线程2又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后线程1操作成功。

尽管线程1的CAS操作成功,但是不代表这个过程就是没有问题的。

原子引用

AtomicReference:原子引用

 //toString,Getter,Setter省略 
class Stu{
    private String name;

    public Stu(String name) {
        this.name = name;
    }

}

public class AtomicReferenceDemo {
    public static void main(String[] args) {
        Stu zs = new Stu("zs");
        Stu ls = new Stu("ls");

        AtomicReference<Stu> reference = new AtomicReference<>(zs);

        System.out.println(reference.compareAndSet(zs,ls));
        System.out.println(reference.compareAndSet(zs,ls));
    }
}

ABA问题解决

新增一种机制,修改版本号(类似时间戳)

存在ABA问题的代码:

        new Thread(()->{
            atomicReference.compareAndSet(10,11);
            atomicReference.compareAndSet(11,10);
        },"t1").start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicReference.compareAndSet(10,11));
        },"t2").start();

使用AtomicStampedReference解决:

    private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(10,1);

    public static void main(String[] args) {
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();
            System.out.println("t3 first stamp :"+stamp);
            atomicStampedReference.compareAndSet(10,11,1,2);
            atomicStampedReference.compareAndSet(11,10,2,3);
        },"t3").start();

        new Thread(()->{
            try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}
            System.out.println(atomicStampedReference.compareAndSet(10, 11, 1, 2));
        },"t4").start();
    }
原文地址:https://www.cnblogs.com/wwjj4811/p/14309053.html