Java原子类实现原理分析

并发包中的原子类能够解决相似num++这样的复合类操做的原子性问题,相比锁机制,使用原子类更精巧轻量,性能开销更小,下面就一块儿来分析下原子类的实现机理。java

悲观的解决方案(阻塞同步)

  咱们知道,num++看似简单的一个操做,其实是由1.读取 2.加一 3.写入 三步组成的,这是个复合类的操做(因此咱们以前提到过的volatile是没法解决num++的原子性问题的),在并发环境下,若是不作任何同步处理,就会有线程安全问题。最直接的处理方式就是加锁算法

synchronized(this){
    num++;
 }

  使用独占锁机制来解决,是一种悲观的并发策略,抱着一副“总有刁民想害朕”的态势,每次操做数据的时候都认为别的线程会参与竞争修改,因此直接加锁。同一刻只能有一个线程持有锁,那其余线程就会阻塞。线程的挂起恢复会带来很大的性能开销,尽管jvm对于非竞争性的锁的获取和释放作了不少优化,可是一旦有多个线程竞争锁,频繁的阻塞唤醒,仍是会有很大的性能开销的。因此,使用synchronized或其余重量级锁来处理显然不够合理。安全

乐观的解决方案(非阻塞同步)

  乐观的解决方案,顾名思义,就是很大度乐观,每次操做数据的时候,都认为别的线程不会参与竞争修改,也不加锁。若是操做成功了那最好;若是失败了,好比中途确有别的线程进入并修改了数据(依赖于冲突检测),也不会阻塞,能够采起一些补偿机制,通常的策略就是反复重试。很显然,这种思想相比简单粗暴利用锁来保证同步要合理的多。多线程

  鉴于并发包中的原子类其实现机理都差不太多,本章咱们就经过AtomicInteger这个原子类来进行分析。咱们先来看看对于num++这样的操做AtomicInteger是如何保证其原子性的。并发

复制代码
 /**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
    }
复制代码

 咱们来分析下incrementAndGet的逻辑:jvm

  1.先获取当前的value值性能

  2.对value加一优化

  3.第三步是关键步骤,调用compareAndSet方法来来进行原子更新操做,这个方法的语义是:网站

    先检查当前value是否等于current,若是相等,则意味着value没被其余线程修改过,更新并返回true。若是不相等,compareAndSet则会返回false,而后循环继续尝试更新。this

  compareAndSet调用了Unsafe类的compareAndSwapInt方法

复制代码
/**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return true if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
复制代码

  Unsafe的compareAndSwapInt是个native方法,也就是平台相关的。它是基于CPU的CAS指令来完成的

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

CAS(Compare-and-Swap)  

  CAS算法是由硬件直接支持来保证原子性的,有三个操做数:内存位置V、旧的预期值A和新值B,当且仅当V符合预期值A时,CAS用新值B原子化地更新V的值,不然,它什么都不作。

 //CMPXCHG

Unsafe的相关用法,而下面是结合网站中的介绍和具体的AtomicInteger类来说解一下其相关的用法。

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); }

}

private volatile int value;

 

 

 

首先能够看到AtomicInteger类在域中声明了这两个私有变量unsafe和valueOffset。其中unsafe实例采用Unsafe类中静态方法getUnsafe()获得,可是这个方法若是咱们写的时候调用会报错,由于这个方法在调用时会判断类加载器,咱们的代码是没有“受信任”的,而在jdk源码中调用是没有任何问题的;valueOffset这个是指类中相应字段在该类的偏移量,在这里具体便是指value这个字段在AtomicInteger类的内存中相对于该类首地址的偏移量。

 

而后能够看一个有一个静态初始化块,这个块的做用便是求出value这个字段的偏移量。具体的方法使用的反射的机制获得value的Field对象,再根据objectFieldOffset这个方法求出value这个变量内存中在该对象中的偏移量。

 

volatile关键字保证了在多线程中value的值是可见的,任何一个线程修改了value值,会将其当即写回内存当中 

public final int getAndSet(int newValue) {  
for (;;) {
int current = get();  
if (compareAndSet(current, newValue)){
return current;
}  
}
   
public final boolean compareAndSet(int expect, int update) {  
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
}

getAndSet这个方法做用为将value值设置为newValue,并返回修改前的value值
在for循环中,保证每次若是compareAndSet这个方法失败以后,能从新进行尝试,直到成功将value值设置为newValue。

 

compareAndSet这个方法主要调用unsafe.compareAndSwapInt这个方法,这个方法有四个参数,其中第一个参数为须要改变的对象,第二个为偏移量(即以前求出来的valueOffset的值),第三个参数为期待的值,第四个为更新后的值。整个方法的做用即为若调用该方法时,value的值与expect这个值相等,那么则将value修改成update这个值,并返回一个true,若是调用该方法时,value的值与expect这个值不相等,那么不作任何操做,并范围一个false。

 

所以之因此在getAndSet方法中调用一个for循环,即保证若是调用compareAndSet这个方法返回为false时,能再次尝试进行修改value的值,直到修改为功,并返回修改前value的值。

 

整个代码能保证在多线程时具备线程安全性,而且没有使用java中任何锁的机制,所依靠的即是Unsafe这个类中调用的该方法具备原子性,这个原子性的保证并非靠java自己保证,而是靠一个更底层的与操做系统相关的特性实现。

 

  CAS的ABA问题

  固然CAS也并不完美,它存在"ABA"问题,倘若一个变量初次读取是A,在compare阶段依然是A,但其实可能在此过程当中,它先被改成B,再被改回A,而CAS是没法意识到这个问题的。CAS只关注了比较先后的值是否改变,而没法清楚在此过程当中变量的变动明细,这就是所谓的ABA漏洞。 

http://ifeve.com/sun-misc-unsafe/

https://blog.csdn.net/sherld/article/details/42492259

相关文章
相关标签/搜索