zoukankan      html  css  js  c++  java
  • 非阻塞式的原子性操作-CAS应用及原理

    一:问题抛出

    假设在出现高并发的情况下对一个整数变量做依次递增操作,下面这两段代码是否会出现问题?

    1.

    public class IntegerTest  {
        private static Integer count = 0;
        synchronized public static void increment() {
            count++;
        }
    }

    2.

    public class AtomicIntegerTest {
        private static AtomicInteger count = new AtomicInteger(0);
        public static void increment() {
            count.getAndIncrement();
        }
    }

    其实在使用Integer的时候,必须加上synchronized保证不会出现并发线程同时访问的情况,而在AtomicInteger中却不用加上synchronized,在这里AtomicInteger是提供原子操作的

    二:先看下AtomicInteger类中属性和初始化的一些源码

    unsafe:对应的是Unsafe类,Java无法直接访问底层操作系统,而是通过本地(native)方法来访问。JDK中有一个类Unsafe,它提供了硬件级别的原子操作。JDK API文档也没有提供任何关于这个类的方法的解释。从描述可以了解到Unsafe提供了硬件级别的操作,比如说获取某个属性在内存中的位置,比如说修改对象的字段值,即使它是私有的。

    value:volatile修饰的变量,内存中其他线程具有可见性。加或减都是对这个变量值进行修改。

    valueOffset:这里指的就是value这个属性在内存中的偏移量(内存中的地址,而不是值),当类被加载时先按顺序初始化static变量和static块,通过unsafe中public native long objectFieldOffset(Field paramField);

    /** Returns the memory address offset of the given static field.

       * The offset is merely used as a means to access a particular field
       * in the other methods of this class.  The value is unique to the given
       * field and the same value should be returned on each subsequent call.
       * 返回指定静态field的内存地址偏移量,在这个类的其他方法中这个值只是被用作一个访问
       * 特定field的一个方式。这个值对于 给定的field是唯一的,并且后续对该方法的调用都应该
       * 返回相同的值。
       *
       * @param field the field whose offset should be returned.
       *              需要返回偏移量的field
       * @return the offset of the given field.
       *         指定field的偏移量
       */
      public native long objectFieldOffset(Field field);

    获取AtomicInteger类属性value在内存中的偏移量,并将偏移量值赋给valueOffset。需要强调valueOffset代表的不是value值在内存中的位置,而是这个属性在内存中的地址。

    三:那么具体看下实现的源码

    1.递增的方法:incrementAndGet()

    getAndIncrement方法是在一个死循环里面调用compareAndSet方法,如果compareAndSet返回失败,就会一直从头开始循环,不会退出getAndIncrement方法,直到compareAndSet返回true。

     2.compareAndSet方法:

    AtomicInteger中Unsafe实例调用compareAndSwapInt方法。

     3.compareAndSwapInt源码:

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

    四:并发情况处理流程:

    1.首先valueOffset获取value的偏移量,假设value=0,valueOffset=0(valueOffset其实是内存地址,便于表达-后面用valueOffset=n表示对应值的地址)。

    2.线程A调用getAndIncrement方法,执行到161行,获取current=0,next=1,准备执行compareAndSet方法

    3.线程B几乎与线程A同时调用getAndIncrement方法,执行完161行后,获取current=0,next=1,并且先于线程A执行compareAndSet方法,此时value=1,valueOffset=1

    4.线程A调用compareAndSet发现预期值(current=0)与内存中对应的值(valueOffset=1,被线程B修改)不相等,即在本线程执行期间有被修改过,则放弃此次修改,返回false。

    5.线程B接着循环,通过get()获取的值是最新的(volatile修饰的value的值会强迫线程从主内存获取),current=1,next=2,然后发现valueOffset=current=1,修改valueOffset=2。

    五:总结下AtomicInteger的getAndIncrement方法之所以比普通Integer加减更适用并发环境:

    1.current代表value最新的值是因为通过get()方法会从主内存读取(volatile,即读取valueOffset对应的值)

    2.能够监测到get()读取到值到cpu执行compareAndSet执行成功之前被别的线程修改成功后的并发情况。

    3.上面强调被别的线程“修改成功”是因为假如出现“ABA”情况是不会被觉察的。

    即:如果一个变量初次读取的时候是A值,如果在这段期间它的值曾经被改成了B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个漏洞称为CAS操作的"ABA"问题。java.util.concurrent包为了解决这个问题,提供了一个带有标记的原子引用类"AtomicStampedReference",它可以通过控制变量值的版本来保证CAS的正确性。大部分情况下ABA问题并不会影响程序并发的正确性,如果需要解决ABA问题,使用传统的互斥同步可能回避原子类更加高效。

  • 相关阅读:
    访问者模式
    中介者模式
    策略模式
    迭代器模式
    责任链模式
    contentProvider模板
    android studio常用快捷键(不断补充)
    jqgrid表格列动态加载的实现
    Android View.onMeasure方法的理解(转载)
    activity的生命周期
  • 原文地址:https://www.cnblogs.com/hupu-jr/p/7927635.html
Copyright © 2011-2022 走看看