volatile不能保证原子性,atomic不仅保证可见性还有原子性CAS分析

     给一个变量加了volatile关键字,就会告诉编译器和JVM的内存模型:这个变量是对所有线程共享的、可见的,每次jvm都会读取最新写入的值并使其最新值在所有CPU可见。volatile似乎是有时候可以代替简单的锁,似乎加了volatile关键字就省掉了锁。但又说volatile不能保证原子性(java程序员很熟悉这句话:volatile仅仅用来保证该变量对所有线程的可见性,但不保证原子性)。这不是互相矛盾吗?

不要将volatile用在getAndOperate场合(这种场合不原子,需要再加锁),仅仅set或者get的场景是适合volatile的

      例如你让一个volatile的integer自增(i++),其实要分成3步:1)读取volatile变量值到local; 2)增加变量的值;3)把local的值写回,让其它的线程可见。这3步的jvm指令为:     

mov    0xc(%r10),%r8d ; Load

inc    %r8d      ; Increment

mov    %r8d,0xc(%r10) ; Store

lock addl $0x0,(%rsp) ; StoreLoad Barrier

 

 JVM指令:从Load到store到内存屏障,一共4步,其中最后一步jvm让这个最新的变量的值在所有线程可见,也就是最后一步让所有的CPU内核都获得了最新的值,但中间的几步(从Load到Store)是不安全的,中间如果其他的CPU修改了值将会丢失

       重点:为什么AtomicXXX具有原子性和可见性?

 1、其实AtomicLong的源码里也用到了volatile,但只是用来读取或写入,见源码:(atomicInteger一样)

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

    private volatile long value;

    public AtomicLong(long initialValue) {

        value = initialValue;

    }

    /**

     * Creates a new AtomicLong with initial value {@code 0}.

     */

    public AtomicLong() {

    }

上面的源码说明了它的可见性是没有问题的,

2、还有一个要点是,atomic(如atomicInteger)使用的是CAS指令来保证变量的原子性的;

// 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"));// 反射出value属性,获取其在内存中的位置    
  } catch (Exception ex) { throw new Error(ex); }    
}    
    
public final boolean compareAndSet(int expect, int update) {   
  return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 

 

    额外:

简单的来说,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则返回V这是一种乐观锁的思路,它相信在它修改之前,没有其它线程去修改它;而Synchronized是一种悲观锁,它认为在它修改之前,一定会有其它线程去修改它,悲观锁效率很低。

其CAS源码核心代码为:

int compare_and_swap (int* reg, int oldval, int newval)

{

  ATOMIC();

  int old_reg_val = *reg;

  if (old_reg_val == oldval)

     *reg = newval;

  END_ATOMIC();

  return old_reg_val;

}

        利用Unsafe类的JNI方法实现,使用CAS指令,可以保证读-改-写是一个原子操作。compareAndSwapInt有4个参数,this - 当前AtomicInteger对象,Offset - value属性在内存中的位置(需要强调的是不是value值在内存中的位置),expect - 预期值,update - 新值,根据上面的CAS操作过程,当内存中的value值等于expect值时,则将内存中的value值更新为update值,并返回true,否则返回false。在这里我们有必要对Unsafe有一个简单点的认识,从名字上来看,不安全,确实,这个类是用于执行低级别的、不安全操作的方法集合,这个类中的方法大部分是对内存的直接操作,所以不安全,但当我们使用反射、并发包时,都间接的用到了Unsafe

 既然有问题,就需要进行优化:

public static class MyLock {  

    private AtomicBoolean locked = new AtomicBoolean(false);  
   public boolean lock() {  
        return locked.compareAndSet(false, true);  
    }  

    locked变量不再是boolean类型而是AtomicBoolean。这个类中有一个compareAndSet()方法,它使用一个期望值和AtomicBoolean实例的值比较,和两者相等,则使用一个新值替换原来的值。在这个例子中,它比较locked的值和false,如果locked的值为false,则把修改为true。
如果值被替换了,compareAndSet()返回true,否则,返回false。

public static void main(String[] args) {  
  
         AtomicInteger atomicInteger = new AtomicInteger(55);//55  
         int addAndGet = atomicInteger.addAndGet(23);//78  
         boolean compareAndSet = atomicInteger.compareAndSet(78, 43);//true  都是78,则更换为43
         int i = atomicInteger.get();//43  
    } 

cas缺点

       虽然使用CAS可以实现非阻塞式的原子性操作,但是会产生ABA问题,关于ABA问题:

       有ABA问题(即在更新前的值是A,但在操作过程中被其他线程更新为B,又更新为 A),这时当前线程认为是可以执行的,其实是发生了不一致现象,如果这种不一致对程序有影响(真正有这种影响的场景很少,除非是在变量操作过程中以此变量为标识位做一些其他的事,比如初始化配置),则需要使用AtomicStampedReference(除了对更新前的原值进行比较,也需要用更新前的 stamp标志位来进行比较)。

总结: 
      可以用CAS在无锁的情况下实现原子操作,但要明确应用场合,非常简单的操作且又不想引入锁可以考虑使用CAS操作,当想要非阻塞地完成某一操作也可以考虑CAS。不推荐在复杂操作中引入CAS,会使程序可读性变差,且难以测试,同时会出现ABA问题

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值