Volatile与Atomic 浅析之CAS

Volatile关键字到底是干什么的?

Volatile变量不具有原子性
Volatile变量如何保证可见性
我们知道现代的CPU为了优化性能,计算时一般不与内存直接交互。一般先把数据从内存读取到CPU内部缓存再进行操作。而不同线程可能由不同的CPU内核执行,
很可能会导致某变量在不同的处理器中保存着2个不同副本的情况,导致数据不一致,产生意料之外的结果。那么java是怎么保证volatile变量在所有线程中的数据都是一致的呢?
若对一个Volatile变量进行赋值,编译后除了生成赋值字节码外,还会生成一个lock指令。该指令是CPU提供的,能实现下面2个功能:
将CPU当前缓存行内的新数据写入内存
将其它CPU核心里包含本变量的缓存行无效化,以强制下次读取时到内存中读取。

Atomic 如何配合 Volatile保证线程安全?
Atomic :通俗的讲就是 对内存的值 拷贝一个副本,并且每个线程都会存在这一个副本,
如果当前的(预期的值)副本值 == CPU内存中的值 ,那么就把新的值写入内存(Volatile)。
如果当前的(预期的值)副本值 != CPU内存中的值 ,那么什么都不做。
执行顺序 :copy内存-on-write内存。

PS: CAS操作是在 :
private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe() ,Unsafe进行的()。
compareAndSet(boolean expect, boolean update)
如图所示:

这里写图片描述

当A线程读到P2内存 变量,通过CAS操作,通过预期的值(expect)与内存中的 V 值比较 如果相等说明,当前没有其它线程去操作这个变量,所以再把 update值 赋值(P1)给 内存中的V 。
如果当前值(expect)与内存中的 V 值比较 如果不相等说明 ,其它线程已经把操作了内存中的值了,就什么都不做。
这就是一次完整的CAS 操作。

比如下面的单例模式:

public class AtomicSingleton {

private static AtomicReference<AtomicSingleton> INSTANCE = new AtomicReference<>();

private AtomicSingleton() {}

public static AtomicSingleton getInstace(){

AtomicSingleton current = INSTANCE.get();

    for (;;) {

        if(current != null){

          return current;

        }

        current = new AtomicSingleton();

        //第一次再内存中 AtomicSingleton 肯定是为NULL 的
        //所以如果当前预期的值为null 与内存中的值相等说明,需要去创建这个对象,
        //预期的值(expect) = current
        if(INSTANCE.compareAndSet(null, current))

         {

             return current;

        }

    }

 }

}
Atomic其他常用接口说明
          int addAndGet(int delta)
          以原子方式将给定值与当前值相加。 功能等价于i=i+delta。

          int getAndAdd(int delta)
          以原子方式将给定值与当前值相加。 功能等价于{int tmp=i;i+=delta;return tmp;}。

          int getAndIncrement()
          以原子方式将当前值加 1。 功能等价于i++。

          int decrementAndGet()
          以原子方式将当前值减 1。 功能等价于--i。

          int getAndDecrement()
          以原子方式将当前值减 1。 功能等价于i--。

          int getAndSet(int newValue)
          以原子方式设置为给定值,并返回旧值。 功能等价于{int tmp=i;i=newValue;return tmp;

场景1:
不过这个也不能完全保证线程安全比如ABA的问题:
如上图说明:
1:当执行A进程
2:A进程读取(p2)CPU内存变量V
3:执行B进程
4:B进程在读取P4CPU内存变量
5:B再把内存变量修改为V1,
6:紧接着B进程又把内存变量修改为了V
7:又开始执行A进程的P1
8:这时候A进程的去对比 CPU内存中的变量发现 expect = V 相等 ,进程就认为当前没有线程去操作这个变量,然后又去执行操作。
如上面的单例来说,多个进程去操作 可能会造成当前可能存在多个AtomicSingleton 对象。

以上的问题如何去解决呢?
AtomicStampedReference 为我们提供另外一个方案:
进行两次对比

 public boolean compareAndSet(V   expectedReference,//期望值
                                 V   newReference, //新变量
                                 int expectedStamp,//读取的同时会再CPU赋值一个变量
                                 int newStamp) { //写的时候发现跟读的时候变量一致说明,当前变量没被其它线程篡改
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值