强大的CAS机制

相信我们都知道乐观锁的底层是利用了CAS机制实现(如有不懂,请看上篇文章)你真的了解乐观锁、悲观锁吗?
Java的CAS底层实现
强大的CAS机制
我们先来看看cnt.incrementAndGet();这个自增方法的源码

public final intincrementAndGet() {
        for (;;) {//失败,循环重试
            int current = get();//读取值
            int next = current + 1;//修改值
            if (compareAndSet(current, next))//比较并且赋值
                return next;
        }
    }

private volatileint value;
   public final int get(){
       return value;
}

复制代码这里需要注意一下这个get方法,为何要在声明value时候使用volatile关键字呢? 那是因为volatile关键字保证变量的可见性!保证获取当前值是内存中的最新值
而这段代码也是是ABA产生的原因
在以上代码中,可以看到compareAndSet(int expect, intupdate)的第1个参数,传进去的并不是版本号,而是数据的旧值。也就是说,它认为,只要数据的旧值expect = 数据当前的值,则说明在此期间没有其他线程修改过此数据,则把数据修改为新值update。
这种比较值,而不是比较版本号的做法,会产生经典的ABA问题。而这,也正是AtomicStampedReference要解决的。

public final boolean compareAndSet(int expect,int update) {
       return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
   private static final Unsafe unsafe = Unsafe.getUnsafe();
   private static final long valueOffset;

   static {
       try {
           //value成员变量在内存中的偏移量
           valueOffset = unsafe.objectFieldOffset
                   (AtomicInteger.class.getDeclaredField("value"));
       } catch (Exception ex) { throw new Error(ex); }
}

复制代码接下来我们看看compareAndSet 方法,很明显我们需要知道什么是unsafe?compareAndSwapInt方法的具体含义,以及这几个参数所表示的意义。
到底什么是unsafe?java不像c/c++可以直接访问底层操作系统,但是JVM却留了一手,unsafe可以为我们提供硬件级别的操作。
compareAndSwapInt方法保证了Compare和Swap操作之间的原子性操作。
CAS机制中使用了3个操作数:需要读写的内存值 V,进行比较的值 A,拟写入的新值 B,而unsafe的compareAndSwapInt方法参数包括三个元素,valueOffset参数代表了V,expect参数代表了A,update参数代表了B
ABA问题
在线程1改数据期间,线程2把数据改为A,再改为B,再改回到A。这个时候,线程1做CAS的时候,如果只是比较值,则它会认为数据在此期间没有被改动过,而实际上数据已被线程2改动过3次。
我们举一个例子
假设银行有一个遵循CAS原理的提款机,小明有100元存款,现在需要取出50元。
由于取款机出现了点小问题,取款操作被提交了两次,开启了两个线程,两个线程都是获取当前余额100元,更新成50元。
理论上一个线程成功了,另一个线程失败了,余额只被扣了一次,余额为50.
但是这时候,线程1执行成功了,线程2因为部分原因阻塞,这时候小明妈妈往小明账户中汇款50元,这时候账户余额为100元,此时线程2恢复运行,因为阻塞之前获取的账户金额为100元,此时账户金额也为100元,线程2认为一致执行成功,余额会被更新为50元。而正确余额应该是100元。
这就是1A-2B-3A问题
那该怎么解决这个问题呢?其实逻辑也很简单,我们只需要加个版本号就行了,在Compare期间不仅要比较A和V中的值,还需要比较版本号是否一致。面我们看看加入版本号的实现
//旧值,新值,旧版本号,新版本号

public boolean compareAndSet(V  expectedReference,
                                 V  newReference,
                                 int  expectedStamp,
                                 int  newStamp)
 {
       ReferenceIntegerPair<V> current = atomicRef.get();
       return  expectedReference ==current.reference &&
           expectedStamp == current.integer &&
           ((newReference == current.reference &&
              newStamp == current.integer) ||
            atomicRef.compareAndSet(current,
                                     newReferenceIntegerPair<V>(newReference,
                                                             newStamp)));
    }

   private static class ReferenceIntegerPair<T> {
       private final T reference;    //值
       private final int integer;    //版本号
       ReferenceIntegerPair(T r, int i) {
           reference = r; integer = i;
       }
    }

复制代码上面的atomicRef.compareAndSet(…)的第一个参数,传入的是一个ReferenceIntegerPair对象,它里面包含了2个字段:值 + 版本号。这也就意味着,它同时比较了值和版本号。
– 值不等,则肯定被其他线程改过了,不用再比较版本号,cas提交失败;
值相等,再比较版本号,如果版本号也相等,则说明真的没有被改过,cas提交成功;
值相等,版本号不等,则就是出现了ABA,CAS提交失败。
----------------END----------------
喜欢本文的朋友,欢迎关注我的公众号程序员阿宝,查看更多精彩内容

强大的CAS机制