java中的CAS和原子類的實現(xiàn)(JDK1.8)

什么是CAS

CAS的全稱為Compare-And-Swap,直譯就是對比交換。是一條CPU的原子指令,其作用是讓CPU先進(jìn)行比較兩個值是否相等,然后原子地更新某個位置的值,經(jīng)過調(diào)查發(fā)現(xiàn),其實現(xiàn)方式是基于硬件平臺的匯編指令,就是說CAS是靠硬件實現(xiàn)的,JVM只是封裝了匯編調(diào)用,那些AtomicInteger類便是使用了這些封裝后的接口。
????簡單解釋:CAS操作需要輸入兩個數(shù)值,一個舊值(期望操作前的值)和一個新值,在操作期間先比較下在舊值有沒有發(fā)生變化,如果沒有發(fā)生變化,才交換成新值,發(fā)生了變化則不交換。
????CAS操作是原子性的,所以多線程并發(fā)使用CAS更新數(shù)據(jù)時,可以不使用鎖。JDK中大量使用了CAS來更新數(shù)據(jù)而防止加鎖(synchronized 重量級鎖)來保持原子更新。
????相信sql大家都熟悉,類似sql中的條件更新一樣:update set id=3 from table where id=2。因為單條sql執(zhí)行具有原子性,如果有多個線程同時執(zhí)行此sql語句,只有一條能更新成功。

如果不使用CAS,在高并發(fā)下,多線程同時修改一個變量的值我們需要synchronized加鎖(可能有人說可以用Lock加鎖,Lock底層的AQS也是基于CAS進(jìn)行獲取鎖的)。

public class Test {
    private int i=0;
    public synchronized int add(){
        return i++;
    }
}

java中為我們提供了AtomicInteger 原子類(底層基于CAS進(jìn)行更新數(shù)據(jù)的),不需要加鎖就在多線程并發(fā)場景下實現(xiàn)數(shù)據(jù)的一致性。

public class Test {
    private  AtomicInteger i = new AtomicInteger(0);
    public int add(){
        return i.addAndGet(1);
    }
}

java.util.concurrent包都中的實現(xiàn)類都是基于volatile和CAS來實現(xiàn)的。尤其java.util.concurrent.atomic包下的原子類。

簡單介紹下volatile特性:

  1. 內(nèi)存可見性(當(dāng)一個線程修改volatile變量的值時,另一個線程就可以實時看到此變量的更新值)
  2. 禁止指令重排(volatile變量之前的變量執(zhí)行先于volatile變量執(zhí)行,volatile之后的變量執(zhí)行在volatile變量之后)

AtomicInteger 源碼解析

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
    static {
        try {
            //用于獲取value字段相對當(dāng)前對象的“起始地址”的偏移量
            valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

    //返回當(dāng)前值
    public final int get() {
        return value;
    }

    //遞增加detla
    public final int getAndAdd(int delta) {
        //三個參數(shù),1、當(dāng)前的實例 2、value實例變量的偏移量 3、當(dāng)前value要加上的數(shù)(value+delta)。
        return unsafe.getAndAddInt(this, valueOffset, delta);
    }

    //遞增加1
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
...
}

我們可以看到 AtomicInteger 底層用的是volatile的變量和CAS來進(jìn)行更改數(shù)據(jù)的。
volatile保證線程的可見性,多線程并發(fā)時,一個線程修改數(shù)據(jù),可以保證其它線程立馬看到修改后的值
CAS 保證數(shù)據(jù)更新的原子性。

Unsafe源碼解析

下面分析下Unsafe 類中的實現(xiàn)。代碼反編譯出來的。

public final int getAndAddInt(Object paramObject, long paramLong, int paramInt)
  {
    int i;
    do
      i = getIntVolatile(paramObject, paramLong);
    while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));
    return i;
  }

  public final long getAndAddLong(Object paramObject, long paramLong1, long paramLong2)
  {
    long l;
    do
      l = getLongVolatile(paramObject, paramLong1);
    while (!compareAndSwapLong(paramObject, paramLong1, l, l + paramLong2));
    return l;
  }

  public final int getAndSetInt(Object paramObject, long paramLong, int paramInt)
  {
    int i;
    do
      i = getIntVolatile(paramObject, paramLong);
    while (!compareAndSwapInt(paramObject, paramLong, i, paramInt));
    return i;
  }

  public final long getAndSetLong(Object paramObject, long paramLong1, long paramLong2)
  {
    long l;
    do
      l = getLongVolatile(paramObject, paramLong1);
    while (!compareAndSwapLong(paramObject, paramLong1, l, paramLong2));
    return l;
  }

  public final Object getAndSetObject(Object paramObject1, long paramLong, Object paramObject2)
  {
    Object localObject;
    do
      localObject = getObjectVolatile(paramObject1, paramLong);
    while (!compareAndSwapObject(paramObject1, paramLong, localObject, paramObject2));
    return localObject;
  }

從源碼中發(fā)現(xiàn),內(nèi)部使用自旋的方式進(jìn)行CAS更新(while循環(huán)進(jìn)行CAS更新,如果更新失敗,則循環(huán)再次重試)。

又從Unsafe類中發(fā)現(xiàn),原子操作其實只支持下面三個方法。

  public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3);

  public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);

  public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);

我們發(fā)現(xiàn)Unsafe只提供了3種CAS方法:compareAndSwapObject、compareAndSwapInt和compareAndSwapLong。都是native方法。

AtomicBoolean 源碼解析

public class AtomicBoolean implements java.io.Serializable {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

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

    private volatile int value;

    public AtomicBoolean(boolean initialValue) {
        value = initialValue ? 1 : 0;
    }
    public final boolean compareAndSet(boolean expect, boolean update) {
        int e = expect ? 1 : 0;
        int u = update ? 1 : 0;
        return unsafe.compareAndSwapInt(this, valueOffset, e, u);
    }
    ...
}

從AtomicBoolean源碼,發(fā)現(xiàn)他底層也是使用volatile類型的int 變量,跟AtomicInteger 實現(xiàn)方式一樣,只不過是把Boolean轉(zhuǎn)換成 0和1進(jìn)行操作。

所以原子更新char、float和double變量也可以轉(zhuǎn)換成int 或long來實現(xiàn)CAS的操作。

CAS缺點

  1. ABA問題。因為CAS需要在操作值的時候檢查下值有沒有發(fā)生變化,如果沒有發(fā)生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那么使用CAS進(jìn)行檢查時會發(fā)現(xiàn)它的值沒有發(fā)生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那么A-B-A 就會變成1A-2B-3A。
    從Java1.5開始JDK的atomic包里提供了一個類AtomicStampedReference來解決ABA問題。這個類的compareAndSet方法作用是首先檢查當(dāng)前引用是否等于預(yù)期引用,并且當(dāng)前標(biāo)志是否等于預(yù)期標(biāo)志,如果全部相等,則以原子方式將該引用和該標(biāo)志的值設(shè)置為給定的更新值。
  2. 循環(huán)時間長開銷大。自旋CAS如果長時間不成功,會給CPU帶來非常大的執(zhí)行開銷。

想了解更多精彩內(nèi)容請關(guān)注我的公眾號

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,362評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,013評論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,346評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,421評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,146評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,534評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,585評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,767評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,318評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,074評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,258評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,828評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,486評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,916評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,156評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,993評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,234評論 2 375

推薦閱讀更多精彩內(nèi)容

  • Java8張圖 11、字符串不變性 12、equals()方法、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,720評論 0 11
  • 從三月份找實習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍(lán)閱讀 42,330評論 11 349
  • 作者: 一字馬胡 轉(zhuǎn)載標(biāo)志 【2017-11-01】 更新日志 日期更新內(nèi)容備注2017-11-01新建文章V1...
    一字馬胡閱讀 7,443評論 9 134
  • (肆)友誼 回到學(xué)校后,我以為我肯定是我們寢室第一個回校的,可誰知,才上樓走到樓道上,便看到寢室門大開著。別的宿舍...
    顧一念閱讀 400評論 8 7
  • 聞雞不見天邊山,臥心苦讀數(shù)十年。浮華好似曇花現(xiàn),修身養(yǎng)性一徳間。圖片發(fā)自簡書App
    昊水長天閱讀 114評論 0 1