AtomicInteger 源码

作者: Skymiles | 来源:发表于2016-10-11 14:39 被阅读348次
基础介绍
    要对AtomicInteger有一个深入的认识,就必须要了解一下悲观锁和乐观锁。
    cpu是时分复用的,也就是把cpu的时间片,分配给不同的线程进程轮流执行,
    时间片与时间片之间,需要进行cpu切换,也就是会发生进程的切换。切换涉及到清空
    寄存器,缓存数据。然后重新加载新的thread所需数据。
    当一个线程被挂起时,加入到阻塞队列,在一定的时间或条件下,在通过
    notify(),notifyAll()唤醒回来。在某个资源不可用的时候,就将cpu让出,
    把当前等待线程切换为阻塞状态。等到资源(比如一个共享数据)可用了,那么就将线程唤醒,
    让他进入runnable状态等待cpu调度。这就是典型的悲观锁的实现。

    但是,由于在进程挂起和恢复执行过程中存在着很大的开销。当一个线程正在等待锁时,
    它不能做任何事,所以悲观锁有很大的缺点。举个例子,如果一个线程需要某个资源,但是
    这个资源的占用时间很短,当线程第一次抢占这个资源时,可能这个资源被占用,如果此时挂起
    这个线程,可能立刻就发现资源可用,然后又需要花费很长的时间重新抢占锁,时间代价
    就会非常的高。

    所以就有了乐观锁的概念,他的核心思路就是,每次不加锁而是假设没有冲突而去完成某项操作,
    如果因为冲突失败就重试,直到成功为止。在上面的例子中,某个线程可以不让出cpu,而是一直
    while循环,如果失败就重试,直到成功为止。所以,当数据争用不严重时,乐观锁效果更好。
    比如我们要说的AtomicInteger底层同步CAS就是一种乐观锁思想的应用。

    CAS就是Compare and Swap的意思,比较并操作。很多的cpu直接支持CAS指令。CAS是项
    乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,
    而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
    CAS有3个操作数,内存值V,预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,

将内存值V修改为B,否则什么都不做。


</br>

CAS操作

CAS通过调用JNI的代码实现的。JNI:Java Native Interface为JAVA本地调用,允许java调用其他语言。而compareAndSwapInt就是借助C来调用CPU底层指令实现的。#lock类似的cpu指令.

AtomicInteger内部有一个变量UnSafe:
private static final Unsafe unsafe = Unsafe.getUnsafe();

Unsafe类是一个可以执行不安全、容易犯错的操作的一个特殊类。虽然Unsafe类中所有方法都是public的,但是这个类只能在一些被信任的代码中使用。其实CAS指的是sun.misc.Unsafe这个类中的一些方法的统称。例如,Unsafe这个类中有compareAndSwapInt、compareAndSwapLong等方法。

public final native boolean compareAndSwapInt(Object o, long V, int E, int N);
  CAS的过程是:它包含了3个参数CAS(O,V,E,N)。O表示要更新的对象。V表示指明更新的对象中的哪个变量,E是进行比较的值,如果V==E,则将N赋值给V。
  第二个参数V(offset),其实要更新的对象里的字段相对于对象初始位置的内存偏移量。通俗一点就是在CAS(O,V,E,N)中,O是你要更新那个对象,
V就是我要通过这个偏移量找到这个对象中的value对象,来对他进行操作。也就是说,如果我把1这个数字属性更新到2的话,需要这样调用:

compareAndSwapInt(this, valueOffset, 1, 2);

valueOffset字段表示内存位置,可以在AtomicInteger对象中使用unsafe得到:

static {
try { 
  valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); 
} catch (Exception ex) {
  throw new Error(ex); }
}

AtomicInteger内部使用变量value表示当前的整型值,这个整型变量还是volatile的,表示内存可见性,一个线程修改value之后保证对其他线程的可见性:

private volatile int value;

AtomicInteger内部还封装了一下CAS,定义了一个compareAndSet方法,只需要2个参数:

public final boolean compareAndSet(int expect, int update) {
 return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

其中
unsafe.compareAndSwapInt(this, valueOffset, expect, update);
类似于:
if (this == expect) {
  this = update;
  return true;
} else {
  return false;
}

具体函数说明
public class AtomicInteger extends Number implements java.io.Serializable {  
    private static final long serialVersionUID = 6214790243416807050L;  
  
  
    private static final Unsafe unsafe = Unsafe.getUnsafe(); //这里是初始化一个Unsafe对象。因为CAS是这个类中的方法。  
    private static final long valueOffset;  
  
    static {  
      try {  
          
          /*一个java对象可以看成是一段内存,各个字段都得按照一定的顺序放在这段内存里, 
          同时考虑到对齐要求,可能这些字段不是连续放置的,用这个方法能准确地告诉你某个 
          字段(也就是下面的value字段)相对于对象的起始内存地址的字节偏移量,因为是相对 
          偏移量,所以它其实跟某个具体对象又没什么太大关系,跟class的定义和虚拟机的内 
          存模型的实现细节更相关。通俗一点就是在CAS(O,V,E,N)中,O是你要更新那个对象, 
          V就是我要通过这个偏移量找到这个对象中的value对象,来对他进行操作。*/  
            
        valueOffset = unsafe.objectFieldOffset  
            (AtomicInteger.class.getDeclaredField("value"));   
      } catch (Exception ex) { throw new Error(ex); }  
    }  
    //volatile,保证变量的可见性。  
    private volatile int value;  
  
    //有参构造  
    public AtomicInteger(int initialValue) {  
        value = initialValue;  
    }  
  
     
    public AtomicInteger() {  
    }  
      
    public final int get() {  
        return value;  
    }  
      
    public final int getAndIncrement() {  
        //为什么会无限循环,先得到当前的值value,然后再把当前的值加1
        //加完之后使用cas原子操作让当前值加1处理正确。当然cas原子操作不一定是成功的,
        //所以做了一个死循环,当cas操作成功的时候返回数据。这里由于使用了cas原子操作,
        //所以不会出现多线程处理错误的问题。
        //比如,
        //   1. Thread-A进入循环获取current=1,然后切下cpu,Thread-B上cpu得到current=1再下cpu;
        //   2. 然后Thread-A的next值为2,进行cas操作并且成功的时候,将value修改成了2;这时候内存中value值为2了,
        //      这个时候Thread-B切上cpu执行的next值为2,当进行cas操作的时候由于expected值已经是2,而不是1了;所以cas操作会失败,
        //   3. 失败了怎么办,在下个循环中得到的current就变成了2;也就不会出现多线程处理问题了!
        for (;;) {  
            int current = get(); //得到当前的值  
            int next = current + 1;  
            if (compareAndSet(current, next))  
                return current;  
        }  
    }  
  
    
    public final boolean compareAndSet(int expect, int update) {  
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);  
    }  
      
} 

CAS缺点

  CAS虽然很高效的解决原子操作,但是CAS仍然存在三大问题。
---ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作。

  1. ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。

  2. 循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

  3. 只能保证一个共享变量的原子操作。当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。

相关文章

  • AtomicInteger 源码

    基础介绍 将内存值V修改为B,否则什么都不做。 CAS操作 CAS通过调用JNI的代码实现的。JNI:Java N...

  • 21.AtomicInteger、AtomicLong

    加减: 源码: U是Unsafe的对象。AtomicInteger的 getAndIncrement() 方法和 ...

  • AtomicInteger源码分析

    在Java的多线程开发中需要做一些同步的操作。在java concurrent库中提供了一系列支持原子操作的类,在...

  • 并发:AtomicInteger 源码

    Java中的AtomicInteger大家应该很熟悉,它是为了解决多线程访问Integer变量导致结果不正确所设计...

  • JDK源码 -- AtomicInteger

    一、概念 类定义: 继承了Number抽象类,说明是个数字类型。 实现了Serializable接口,可以进行序列...

  • AtomicInteger源码分析

    **AtomicInteger** 是Java提供的原子操作类,其内部通过 **UnSafe** 工具类,使用 =...

  • AtomicInteger 源码分析

    var1,var2联合定位字段的内存地址 compareAndSwapInt 是CAS操作更新新值,当compar...

  • AtomicInteger源码解析

    此文已由作者赵计刚授权网易云社区发布。 欢迎访问网易云社区,了解更多网易技术产品运营经验。 1、原子类 可以实现一...

  • AtomicInteger源码解析

    此文已由作者赵计刚授权网易云社区发布。 欢迎访问网易云社区,了解更多网易技术产品运营经验。 1、原子类 可以实现一...

  • 源码解读-AtomicInteger

    前面AtomicBoolean中对原子更新值已经讲得差不多了,AtomicInteger实现的核心也跟Atomic...

网友评论

    本文标题:AtomicInteger 源码

    本文链接:https://www.haomeiwen.com/subject/mhkbyttx.html