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閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容

  • 一、線程 1.1 線程的概述 一個運行程序就是一個進程,而線程是進程中獨立運行的子任務 線程是操作系統執行流中的最...
    itcjj閱讀 965評論 0 8
  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區別 13、...
    Miley_MOJIE閱讀 3,725評論 0 11
  • CAS簡歷 CAS(Compare and swap)比較和替換是設計并發算法時用到的一種技術 。Compare ...
    classtag閱讀 4,158評論 2 37
  • 無題 劉榮(南宮靈羽) 龍城荷花已然凋,又見詞人在此消。 儺城美景依舊在,夢回鄉里...
    南宮靈羽閱讀 125評論 0 1
  • oc學習
    KevinSR閱讀 143評論 0 0