zoukankan      html  css  js  c++  java
  • 浅析CAS与AtomicInteger原子类

    一:CAS简介

    CAS:Compare And Swap(字面意思是比较与交换),JUC包中大量使用到了CAS,比如我们的atomic包下的原子类就是基于CAS来实现。区别于悲观锁synchronized,CAS是乐观锁的一种实现,在某些场合使用它可以提高我们的并发性能。

    在CAS中,主要是涉及到三个操作数,所期盼的旧值、当前工作内存中的值、要更新的值,仅当所期盼的旧值等于当前值时,才会去更新新值。

    二:CAS举例

    比如当如下场景,由于i++是个复合操作,读取、自增、赋值三步操作,因此在多线程条件下我们需要保证i++操作的安全

    public class CASTest {
        int i = 0;
    
        public void increment() {
            i++;
        }
    }

    解决办法有通过使用synchronized来解决,synchronized解决了并发编程的原子性,可见性,有序性。

    public class CASTest {
        int i = 0;
    
        public synchronized  void increment() {
            i++;
        }
    }

    但synchronized毕竟是悲观锁,尽管它后续进行了若干优化,引入了锁的膨胀升级措施,但是还是存在膨胀为重量级锁而导致阻塞问题,因此,我们可以使用基于CAS实现的原子类AtomicInteger来保证其原子性

    public class CASTest {
        AtomicInteger i = new AtomicInteger(0);
        public  static void increment() {
            //自增并返回新值
            i.incrementAndGet();
        }
    }

    三:CAS原理分析

    atomic包下的原子类就是基于CAS实现的,我们拿AtomicInteger来分析下CAS.

    public class AtomicInteger extends Number implements java.io.Serializable {
        private static final long serialVersionUID = 6214790243416807050L;
    
        // CAS操作是基于一个Unsafe类,Unsafe类是整个Concurrent包的基础,里面所有的函数都是native的
        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); }
        }
        //底层采用volatile修饰值,保证其可见性和有序性
        private volatile int value;

    从AtomicInteger定义的相关属性来看,其内部的操作都是基于Unsafe类,因为在Java中,我们并不能直接操作内存,但是Java还是开放了一个Unsafe类来给我们进行操作,顾名思义,Unsafe,是不安全的,因此要谨慎使用。

    其内部定义的值是用volatiel进行修饰的,volatile可以保证有序性和可见性,具体为什么可以保证就不在此阐述了。

    再来看看其几个核心的API

    //以原子方式将值设置为给定的新值 expect:期望值 update:旧值
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
    //以原子方式将当前值+1,返回期望值
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
    
    //以原子方式将当前值-1,返回期望值        
    public final int decrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
    }

    关于其源码还是很少的,基本都是基于Unsafe类进行实现的。

    先来看看compareAndSet方法,其调用的是Unsafe的compareAndSwapInt方法,当工作内存中的值与所期盼的旧值不相同的时候,会更新失败,举例说明:

    public class CASDemo {
        public static void main(String[] args) {
            AtomicInteger atomicInteger = new AtomicInteger(2020);
            System.out.println("更新结果:"+atomicInteger.compareAndSet(2020, 2021));
            System.out.println("当前值为:"+atomicInteger.get());
    
            //自增加一
            atomicInteger.getAndIncrement();
    
            System.out.println("更新结果:"+atomicInteger.compareAndSet(2020, 2021));
            System.out.println("当前值为:"+atomicInteger.get());
        }
    }

     在来看看incrementAndGet方法,其调用的是unsafe.getAndAddInt方法,其就相当于是自旋锁的实现,当所期盼的旧值与新值相同时才更新成功,否则就进行自旋操作直到更新成功为止。

    public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    
        return var5;
    }

    四:CAS缺点分析

    CAS的优点很明显,基于乐观锁的思想,提高了并发情况下的性能,缺点主要是ABA问题、自旋时间过长导致CPU占有率过高、只能保证一个共享变量的原子性。

    ABA问题

    就是一个值由A变为B,在由B变为A,使用CAS操作无法感知到该种情况下出现的变化,带来的后果很严重,比如银行内部员工,从系统挪走一百万,之后还了回来,系统感知不到岂不是要出事。模拟下出现ABA问题:
       public class ABA {
           private static AtomicInteger atomicInteger = new AtomicInteger(0);
       
           public static void main(String[] args) {
               //线程t1实现0->1->0
               Thread t1 = new Thread(new Runnable() {
                   @Override
                   public void run() {
                       atomicInteger.compareAndSet(0,1);
                       atomicInteger.compareAndSet(1,0);
                   }
               },"t1");
       
               //线程t2实现0->100
               Thread t2 = new Thread(new Runnable() {
                   @Override
                   public void run() {
                       try {
                           //模拟狸猫换太子行为
                           TimeUnit.SECONDS.sleep(2);
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                       System.out.println("更新结果:"+atomicInteger.compareAndSet(0, 100));
                   }
               });
       
               t1.start();
               t2.start();
           }
       }
       

    运行结果是:true

    解决ABA可以使每一次修改都带上时间戳,以记录版本号的形式来使的CAS感知到这种狸猫换太子的操作。Java提供了AtomicStampedReference类来解决,该类除了指定旧值与期盼值,还要指定旧的版本号与期盼的版本号

        public boolean compareAndSet(V   expectedReference, V   newReference, int expectedStamp, 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)));
        }
    public class ABA_Test {
    
        // 初始值100,版本号1
        private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100, 1);
    
        public static void main(String[] args) throws InterruptedException {
            // AtomicStampedReference实现
            Thread tsf1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // 让 tsf2先获取stamp,导致预期时间戳不一致
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 预期引用:100,更新后的引用:110,预期标识getStamp() 更新后的标识getStamp() + 1
                    atomicStampedReference.compareAndSet(100, 110, atomicStampedReference.getStamp(),
                            atomicStampedReference.getStamp() + 1);
                    atomicStampedReference.compareAndSet(110, 100, atomicStampedReference.getStamp(),
                            atomicStampedReference.getStamp() + 1);
                }
            });
    
            Thread tsf2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    int stamp = atomicStampedReference.getStamp();
    
                    try {
                        TimeUnit.SECONDS.sleep(2); // 线程tsf1执行完
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(
                            "AtomicStampedReference:" + atomicStampedReference.compareAndSet(100, 120, stamp, stamp + 1));
                }
            });
    
            tsf1.start();
            tsf2.start();
        }
    }

    运行结果:

    自旋次数过长

     CAS是基于乐观锁的思想实现的,当频繁出现当前值与所旧预期值不相等的情况,会导致频繁的自旋而使得浪费CPU资源。

    只能保证单个共享变量的原子性

    单纯对共享变量进行CAS操作,只能保证单个,无法使多个共享变量同时进行原子操作。

    参考资料

    狂神说Java:www.bilibili.com/video/BV1B7…
    CAS机制及AtomicInteger源码分析:juejin.im/post/5e2182…

     
  • 相关阅读:
    NET 中反射的用法
    Prism 框架解读之一系列
    委托(Delegate)
    Python NameError:name ‘xrange’ is not defined
    Python import commands ImportError: No module named 'commands'
    Python import commands ImportError: No module named 'commands'
    Python3 TypeError: initial_value must be str or None, not bytes
    Python3 TypeError: initial_value must be str or None, not bytes
    Python import urllib2 ImportError: No module named 'urllib2'
    Python import urllib2 ImportError: No module named 'urllib2'
  • 原文地址:https://www.cnblogs.com/zengcongcong/p/12751761.html
Copyright © 2011-2022 走看看