非阻塞式的原子性操做-CAS应用及原理

一:问题抛出java

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

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是提供原子操做的this

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

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

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

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

/** 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问题,使用传统的互斥同步可能回避原子类更加高效。

相关文章
相关标签/搜索