AtomicInteger 源碼

基礎(chǔ)介紹
    要對AtomicInteger有一個(gè)深入的認(rèn)識,就必須要了解一下悲觀鎖和樂觀鎖。
    cpu是時(shí)分復(fù)用的,也就是把cpu的時(shí)間片,分配給不同的線程進(jìn)程輪流執(zhí)行,
    時(shí)間片與時(shí)間片之間,需要進(jìn)行cpu切換,也就是會發(fā)生進(jìn)程的切換。切換涉及到清空
    寄存器,緩存數(shù)據(jù)。然后重新加載新的thread所需數(shù)據(jù)。
    當(dāng)一個(gè)線程被掛起時(shí),加入到阻塞隊(duì)列,在一定的時(shí)間或條件下,在通過
    notify(),notifyAll()喚醒回來。在某個(gè)資源不可用的時(shí)候,就將cpu讓出,
    把當(dāng)前等待線程切換為阻塞狀態(tài)。等到資源(比如一個(gè)共享數(shù)據(jù))可用了,那么就將線程喚醒,
    讓他進(jìn)入runnable狀態(tài)等待cpu調(diào)度。這就是典型的悲觀鎖的實(shí)現(xiàn)。

    但是,由于在進(jìn)程掛起和恢復(fù)執(zhí)行過程中存在著很大的開銷。當(dāng)一個(gè)線程正在等待鎖時(shí),
    它不能做任何事,所以悲觀鎖有很大的缺點(diǎn)。舉個(gè)例子,如果一個(gè)線程需要某個(gè)資源,但是
    這個(gè)資源的占用時(shí)間很短,當(dāng)線程第一次搶占這個(gè)資源時(shí),可能這個(gè)資源被占用,如果此時(shí)掛起
    這個(gè)線程,可能立刻就發(fā)現(xiàn)資源可用,然后又需要花費(fèi)很長的時(shí)間重新?lián)屨兼i,時(shí)間代價(jià)
    就會非常的高。

    所以就有了樂觀鎖的概念,他的核心思路就是,每次不加鎖而是假設(shè)沒有沖突而去完成某項(xiàng)操作,
    如果因?yàn)闆_突失敗就重試,直到成功為止。在上面的例子中,某個(gè)線程可以不讓出cpu,而是一直
    while循環(huán),如果失敗就重試,直到成功為止。所以,當(dāng)數(shù)據(jù)爭用不嚴(yán)重時(shí),樂觀鎖效果更好。
    比如我們要說的AtomicInteger底層同步CAS就是一種樂觀鎖思想的應(yīng)用。

    CAS就是Compare and Swap的意思,比較并操作。很多的cpu直接支持CAS指令。CAS是項(xiàng)
    樂觀鎖技術(shù),當(dāng)多個(gè)線程嘗試使用CAS同時(shí)更新同一個(gè)變量時(shí),只有其中一個(gè)線程能更新變量的值,
    而其它線程都失敗,失敗的線程并不會被掛起,而是被告知這次競爭中失敗,并可以再次嘗試。
    CAS有3個(gè)操作數(shù),內(nèi)存值V,預(yù)期值A(chǔ),要修改的新值B。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),

將內(nèi)存值V修改為B,否則什么都不做。


</br>

CAS操作

CAS通過調(diào)用JNI的代碼實(shí)現(xiàn)的。JNI:Java Native Interface為JAVA本地調(diào)用,允許java調(diào)用其他語言。而compareAndSwapInt就是借助C來調(diào)用CPU底層指令實(shí)現(xiàn)的。#lock類似的cpu指令.

AtomicInteger內(nèi)部有一個(gè)變量UnSafe:
private static final Unsafe unsafe = Unsafe.getUnsafe();

Unsafe類是一個(gè)可以執(zhí)行不安全、容易犯錯(cuò)的操作的一個(gè)特殊類。雖然Unsafe類中所有方法都是public的,但是這個(gè)類只能在一些被信任的代碼中使用。其實(shí)CAS指的是sun.misc.Unsafe這個(gè)類中的一些方法的統(tǒng)稱。例如,Unsafe這個(gè)類中有compareAndSwapInt、compareAndSwapLong等方法。

public final native boolean compareAndSwapInt(Object o, long V, int E, int N);
??CAS的過程是:它包含了3個(gè)參數(shù)CAS(O,V,E,N)。O表示要更新的對象。V表示指明更新的對象中的哪個(gè)變量,E是進(jìn)行比較的值,如果V==E,則將N賦值給V。
??第二個(gè)參數(shù)V(offset),其實(shí)要更新的對象里的字段相對于對象初始位置的內(nèi)存偏移量。通俗一點(diǎn)就是在CAS(O,V,E,N)中,O是你要更新那個(gè)對象,
V就是我要通過這個(gè)偏移量找到這個(gè)對象中的value對象,來對他進(jìn)行操作。也就是說,如果我把1這個(gè)數(shù)字屬性更新到2的話,需要這樣調(diào)用:

compareAndSwapInt(this, valueOffset, 1, 2);

valueOffset字段表示內(nèi)存位置,可以在AtomicInteger對象中使用unsafe得到:

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

AtomicInteger內(nèi)部使用變量value表示當(dāng)前的整型值,這個(gè)整型變量還是volatile的,表示內(nèi)存可見性,一個(gè)線程修改value之后保證對其他線程的可見性:

private volatile int value;

AtomicInteger內(nèi)部還封裝了一下CAS,定義了一個(gè)compareAndSet方法,只需要2個(gè)參數(shù):

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

具體函數(shù)說明
public class AtomicInteger extends Number implements java.io.Serializable {  
    private static final long serialVersionUID = 6214790243416807050L;  
  
  
    private static final Unsafe unsafe = Unsafe.getUnsafe(); //這里是初始化一個(gè)Unsafe對象。因?yàn)镃AS是這個(gè)類中的方法。  
    private static final long valueOffset;  
  
    static {  
      try {  
          
          /*一個(gè)java對象可以看成是一段內(nèi)存,各個(gè)字段都得按照一定的順序放在這段內(nèi)存里, 
          同時(shí)考慮到對齊要求,可能這些字段不是連續(xù)放置的,用這個(gè)方法能準(zhǔn)確地告訴你某個(gè) 
          字段(也就是下面的value字段)相對于對象的起始內(nèi)存地址的字節(jié)偏移量,因?yàn)槭窍鄬?
          偏移量,所以它其實(shí)跟某個(gè)具體對象又沒什么太大關(guān)系,跟class的定義和虛擬機(jī)的內(nèi) 
          存模型的實(shí)現(xiàn)細(xì)節(jié)更相關(guān)。通俗一點(diǎn)就是在CAS(O,V,E,N)中,O是你要更新那個(gè)對象, 
          V就是我要通過這個(gè)偏移量找到這個(gè)對象中的value對象,來對他進(jìn)行操作。*/  
            
        valueOffset = unsafe.objectFieldOffset  
            (AtomicInteger.class.getDeclaredField("value"));   
      } catch (Exception ex) { throw new Error(ex); }  
    }  
    //volatile,保證變量的可見性。  
    private volatile int value;  
  
    //有參構(gòu)造  
    public AtomicInteger(int initialValue) {  
        value = initialValue;  
    }  
  
     
    public AtomicInteger() {  
    }  
      
    public final int get() {  
        return value;  
    }  
      
    public final int getAndIncrement() {  
        //為什么會無限循環(huán),先得到當(dāng)前的值value,然后再把當(dāng)前的值加1
        //加完之后使用cas原子操作讓當(dāng)前值加1處理正確。當(dāng)然cas原子操作不一定是成功的,
        //所以做了一個(gè)死循環(huán),當(dāng)cas操作成功的時(shí)候返回?cái)?shù)據(jù)。這里由于使用了cas原子操作,
        //所以不會出現(xiàn)多線程處理錯(cuò)誤的問題。
        //比如,
        //   1. Thread-A進(jìn)入循環(huán)獲取current=1,然后切下cpu,Thread-B上cpu得到current=1再下cpu;
        //   2. 然后Thread-A的next值為2,進(jìn)行cas操作并且成功的時(shí)候,將value修改成了2;這時(shí)候內(nèi)存中value值為2了,
        //      這個(gè)時(shí)候Thread-B切上cpu執(zhí)行的next值為2,當(dāng)進(jìn)行cas操作的時(shí)候由于expected值已經(jīng)是2,而不是1了;所以cas操作會失敗,
        //   3. 失敗了怎么辦,在下個(gè)循環(huán)中得到的current就變成了2;也就不會出現(xiàn)多線程處理問題了!
        for (;;) {  
            int current = get(); //得到當(dāng)前的值  
            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缺點(diǎn)

??CAS雖然很高效的解決原子操作,但是CAS仍然存在三大問題。
---ABA問題,循環(huán)時(shí)間長開銷大和只能保證一個(gè)共享變量的原子操作。

??1. ABA問題。因?yàn)镃AS需要在操作值的時(shí)候檢查下值有沒有發(fā)生變化,如果沒有發(fā)生變化則更新,但是如果一個(gè)值原來是A,變成了B,又變成了A,那么使用CAS進(jìn)行檢查時(shí)會發(fā)現(xiàn)它的值沒有發(fā)生變化,但是實(shí)際上卻變化了。ABA問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時(shí)候把版本號加一,那么A-B-A 就會變成1A-2B-3A。

??2. 循環(huán)時(shí)間長開銷大。自旋CAS如果長時(shí)間不成功,會給CPU帶來非常大的執(zhí)行開銷。如果JVM能支持處理器提供的pause指令那么效率會有一定的提升,pause指令有兩個(gè)作用,第一它可以延遲流水線執(zhí)行指令(de-pipeline),使CPU不會消耗過多的執(zhí)行資源,延遲的時(shí)間取決于具體實(shí)現(xiàn)的版本,在一些處理器上延遲時(shí)間是零。第二它可以避免在退出循環(huán)的時(shí)候因內(nèi)存順序沖突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執(zhí)行效率。

??3. 只能保證一個(gè)共享變量的原子操作。當(dāng)對一個(gè)共享變量執(zhí)行操作時(shí),我們可以使用循環(huán)CAS的方式來保證原子操作,但是對多個(gè)共享變量操作時(shí),循環(huán)CAS就無法保證操作的原子性,這個(gè)時(shí)候就可以用鎖,或者有一個(gè)取巧的辦法,就是把多個(gè)共享變量合并成一個(gè)共享變量來操作。比如有兩個(gè)共享變量i=2,j=a,合并一下ij=2a,然后用CAS來操作ij。從Java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個(gè)變量放在一個(gè)對象里來進(jìn)行CAS操作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、線程 1.1 線程的概述 一個(gè)運(yùn)行程序就是一個(gè)進(jìn)程,而線程是進(jìn)程中獨(dú)立運(yùn)行的子任務(wù) 線程是操作系統(tǒng)執(zhí)行流中的最...
    itcjj閱讀 1,049評論 0 8
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,894評論 0 11
  • CAS簡歷 CAS(Compare and swap)比較和替換是設(shè)計(jì)并發(fā)算法時(shí)用到的一種技術(shù) 。Compare ...
    classtag閱讀 4,208評論 2 37
  • 無題 劉榮(南宮靈羽) 龍城荷花已然凋,又見詞人在此消。 儺城美景依舊在,夢回鄉(xiāng)里...
    南宮靈羽閱讀 151評論 0 1
  • oc學(xué)習(xí)
    KevinSR閱讀 163評論 0 0

友情鏈接更多精彩內(nèi)容