CAS

1什么是CAS

    CAS(Compare And Swap),即比较并交换,是解决多线程并行情况下使用锁造成性能损耗的一种机制。

    CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。
更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

1.1在内存地址V当中,存储着值为10的变量。

1.2此时线程1想要把变量的值增加1。对线程1来说,旧的预期值A=10,要修改的新值B=11。


1.3在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。

1.4线程1开始提交更新,首先进行A和地址V的实际值比较(Compare),发现A不等于V的实际值,提交失败。


1.5线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋。


1.6这一次比较幸运,没有其他线程改变地址V的值。线程1进行Compare,发现A和地址V的实际值是相等的。

image.png

1.7线程1进行SWAP,把地址V的值替换为B,也就是12。

从思想上来说,Synchronized属于悲观锁,悲观地认为程序中的并发情况严重,所以严防死守。CAS属于乐观锁,乐观地认为程序中的并发情况不那么严重,所以让线程不断去尝试更新。

在CAS中,比较和替换是一组原子操作,不会被外部打断,且在性能上更占有优势。

Unsafe提供了三个方法用于CAS操作

public final native boolean compareAndSwapObject(Object value, long valueOffset, Object expect, Object update);

public final native boolean compareAndSwapInt(Object value, long valueOffset, int expect, int update);

public final native boolean compareAndSwapLong(Object value, long valueOffset, long expect, long update);

解析AtomicInteger

当我们使用 AtomicInteger 实现多线程的加操作时,分析源码

public class AtomicInteger extends Number implements java.io.Serializable {

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

   //带参数的构造函数
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
   //缺省构造函数
    public AtomicInteger() {
    }

    public final int get() {
        return value;
    }

    public final void set(int newValue) {
        value = newValue;
    }

    public final void lazySet(int newValue) {
        unsafe.putOrderedInt(this, valueOffset, newValue);
    }

    public final int getAndSet(int newValue) {
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }

    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    public final boolean weakCompareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

    public final int getAndDecrement() {
        return unsafe.getAndAddInt(this, valueOffset, -1);
    }

    public final int getAndAdd(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }

    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

    public final int decrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
    }

    public final int addAndGet(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
    }

    public final int getAndUpdate(IntUnaryOperator updateFunction) {
        int prev, next;
        do {
            prev = get();
            next = updateFunction.applyAsInt(prev);
        } while (!compareAndSet(prev, next));
        return prev;
    }

    public final int updateAndGet(IntUnaryOperator updateFunction) {
        int prev, next;
        do {
            prev = get();
            next = updateFunction.applyAsInt(prev);
        } while (!compareAndSet(prev, next));
        return next;
    }

    public final int getAndAccumulate(int x,
                                      IntBinaryOperator accumulatorFunction) {
        int prev, next;
        do {
            prev = get();
            next = accumulatorFunction.applyAsInt(prev, x);
        } while (!compareAndSet(prev, next));
        return prev;
    }

    public final int accumulateAndGet(int x,
                                      IntBinaryOperator accumulatorFunction) {
        int prev, next;
        do {
            prev = get();
            next = accumulatorFunction.applyAsInt(prev, x);
        } while (!compareAndSet(prev, next));
        return next;
    }

    public String toString() {
        return Integer.toString(get());
    }

    public int intValue() {
        return get();
    }

    public long longValue() {
        return (long)get();
    }

    public float floatValue() {
        return (float)get();
    }

    public double doubleValue() {
        return (double)get();
    }

}

解析

  • Unsafe,是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。

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

  • 变量value用volatile修饰,保证了多线程之间的内存可见性. 我们在使用cas更新value的时候,没有用到volatile,但get、set方法获取value时用到了volatile的可见性。这样结合来看使得AtomicInteger类获取的是主存中最新的值 且 更新时cas是原子性操作。

  • 假设线程A和线程B同时执行getAndAdd操作, 因为比较与替换是原子性操作,所以即使在var5获取到相同的值,也会是顺序的更新。

CAS缺点

  • ABA问题。

如果在这段期间曾经被改成B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。针对这种情况,java并发包中提供了一个带有标记的原子引用类 AtomicStampedReference,它可以通过控制变量值的版本来保证CAS的正确性, 该类提供一个引用和计数变量。

  • 循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。

  • 只能保证一个共享变量的原子操作。

当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性。

从Java1.5开始JDK提供了 AtomicReference 类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

AtomicReference与AtomicInteger的不同

AtomicInteger是对整数的封装,底层采用的是compareAndSwapInt实现CAS,比较的是数值是否相等。
AtomicReference则对应普通的对象引用,底层使用的是compareAndSwapObject实现CAS,比较的是两个对象的地址是否相等。也就是它可以保证你在修改对象引用时的线程安全性。也就是它可以保证你在修改对象引用时的线程安全性。

小例子

public class Main{
    public static void main(String[] args) {
 
        // 创建两个Person对象,它们的id分别是101和102。
        Person2 p1 = new Person2(101);
        Person2 p2 = new Person2(102);
        // 新建AtomicReference对象,初始化它的值为p1对象
        AtomicReference ar = new AtomicReference(p1);
        //更改p1的id.
        p1.setId(106);
        // 通过CAS设置ar。如果ar的值为p1的话,则将其设置为p2。
        ar.compareAndSet(p1, p2);
 
        Person2 p3 = (Person2)ar.get();
        System.out.println("p3 is "+p3);
        System.out.println("p3.equals(p1)="+p3.equals(p1));
    }
 
}
class Person2 {
    volatile long id;
    public Person2(long id) {
        this.id = id;
    }
    public String toString() {
        return "id:"+id;
    }
    public void setId(long id){
        this.id=id;
    }
}

运行结果如下:

p3 is id:102
p3.equals(p1)=false

总结

修改了p1的id对compareAndSet()并没有影响,因为修改id仅仅改变了p1的成员变量,而AtomicReference底层使用的是compareAndSwapObject实现CAS,比较的是两个对象的地址是否相等。也就是它可以保证你在修改对象引用时的线程安全性。上面代码成功的将原子类中的引用从p1变成p2,而且是线程安全的。

参考文献

https://www.cnblogs.com/princessd8251/articles/5187403.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。