AtomicInteger 源码

基础介绍
    要对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操作。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 一、线程 1.1 线程的概述 一个运行程序就是一个进程,而线程是进程中独立运行的子任务 线程是操作系统执行流中的最...
    itcjj阅读 937评论 0 8
  • Java8张图 11、字符串不变性 12、equals()方法、hashCode()方法的区别 13、...
    Miley_MOJIE阅读 3,690评论 0 11
  • CAS简历 CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术 。Compare ...
    classtag阅读 4,145评论 2 37
  • 无题 刘荣(南宫灵羽) 龙城荷花已然凋,又见词人在此消。 傩城美景依旧在,梦回乡里...
    南宫灵羽阅读 114评论 0 1
  • oc学习
    KevinSR阅读 137评论 0 0