注册

CAS以及Atomic原子操作详解

CAS以及Atomic原子操作详解


CAS




  • 什么是CAS



    • 针对一个变量,首先比较它在内存中的值与某个期望的值是否相同,如果相同就给它赋予新值
    • 其原子性是直接在硬件层面得到保障
    • CAS是一种无锁算法,在不使用锁的情况下实现多线程之间的变量同步



  • 底层: CAS的底层实现



    • 从JVM源码层面的看CAS

      • 原子性

        • 在单核处理器是通过cmpxchgl指令来保证原子性的,在多核处理器下无法保证了,通过lock前缀的加持变为lock cmpxchgl保证了原子性,这里lock前缀指令拥有保证后续指令的原子性的作用


      • 有序性

        • 通过C++关键字volatile禁止指令重排序保证有序性,对于C++关键字volatile有两个作用一个是禁止重排序,一个是防止代码被优化


      • 其中可见性在JVM源码层面是保证的了,因为多核处理器下会加lock前缀指令,但是Java代码层面实现的CAS不能保证get加锁标记和set加锁标记的可见性,比如Atomic类中需要通过volatile修饰state保证可见性





  • 缺陷: CAS的缺陷



    • 一般CAS都是配合自旋,自旋时间过长,可能会导致CPU满载,所以一般会选择自旋到一定次数去park
    • 每次只能保证一个共享变量进行原子操作
    • ABA问题

      • 问题: 什么是ABA问题

        • 当有多个线程对一个原子类进行操作时,某个线程在这段时间内将A修改到B,又马上将其修改为A,其他线程并不感知,还是会被修改成功


      • 问题: ABA问题的解决方案

        • 数据库有个锁是乐观锁,是一种通过版本号方式来进行数据同步,也就是每次更新的时候都会匹配这个版本号,只有符号才能更新成功,同样的ABA问题也是基于这种去解决的,相应的Java也提供了对应的原子类AtomicStampedRefrence,其内部reference就是我们实际存储的变量,stamp就是版本号,每次修改可以通过加1来保证版本的唯一性







  • 问题: CAS失败自旋的操作存在什么问题



    • CAS自旋时间过长不成功,会给CPU带来较大的开销



  • CAS的应用




    • CAS操作的是由Unsafe类提供支持,该类定义了三种针对不同类型变量的CAS操作


      public final native boolean compareAndSwapObject(Object o, long offset,Object expected,Object x);
      public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
      public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);





Atomic原子类



  • 在并发编程中很容易出现并发安全的问题,比如自增操作,有可能不能获取正确的值,一般情况想到的是synchronized来保证线程安全,但是由于它是悲观锁,并不是最高效的解决方案,所以Juc提供了乐观锁的方式去提升性能

    • 基本类型: AtomicInteger、AtomicLong、AtomicBoolean
    • 引用类型: AtomicReference、AtomicStampedRerence、AtomicMarkableReference
    • 数组类型: AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
    • 对象属性原子修改器: AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
    • 原子类型累加器(JDK8增加的类): DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder、Striped64



LongAdder和DoubleAdder


瓶颈详解



  • 对于高并发场景下,多个线程同时进行自旋操作,会出现大量失败并不断自旋的情况,此时AtomicLong自旋会成为瓶颈,LongAdder引入解决了高并发场景,AtomicInteger、AtomicLong的自旋瓶颈问题

LongAdder原理


image.png

  • AtomicLong中有个内部变量value保存着实际的值,所有的操作都是针对该变量进行,在高并发场景下,value变量其实就是一个热点,多个线程同时竞争这个热点,而这样冲突的概率就比较大了
  • 重点: LongAdder的基本思路就是分散热点,将value的值分散到一个数组中,不同线程会命中到这个数组的不同槽位中,各个线程只对自己槽位中的那个值进行CAS操作,这样就分散了热点,冲突的概率就小很多,如果要获取真正的值,只需要将各个槽位的值累加返回
  • LongAdder设计的精妙之处: 尽量减少热点冲突,不到最后万不得已,尽量将CAS操作延迟
  • 注意: LongAdder的sum方法会有线程安全的问题

    • 高并发场景下除非全局加锁,否则得不到程序运行中某个时刻绝对准确的值,由于计算总和时没有对Cell数组进行加锁,所以在累加过程中可能有其他线程对于Cell数组中的值因为线程安全无法保障进行了修改,也有可能对数组进行了扩容,所以sum返回的值并不是非常精确的,其返回值并不是一个调用sum方法的原子快照值



LongAdder逻辑


image.png

LongAccumulator



  • LongAccumulator是LongAdder的增强版本,LongAdder只针对数组值进行加减运算,而LongAccumulator提供了自定义的函数操作
  • LongAccumulator内部原理和LongAdder几乎完全一样,都是利用了父类Striped64的longAccumulate方法

作者:枫度柚子
链接:https://juejin.cn/post/7101216131397976071
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册