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

什么是CAS

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

如果不使用CAS,在高并發(fā)下烙懦,多線程同時(shí)修改一個(gè)變量的值我們需要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ā)場(chǎng)景下實(shí)現(xiàn)數(shù)據(jù)的一致性亏较。

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

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

簡單介紹下volatile特性:

  1. 內(nèi)存可見性(當(dāng)一個(gè)線程修改volatile變量的值時(shí)雪情,另一個(gè)線程就可以實(shí)時(shí)看到此變量的更新值)
  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字段相對(duì)當(dāng)前對(duì)象的“起始地址”的偏移量
            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) {
        //三個(gè)參數(shù)你辣,1巡通、當(dāng)前的實(shí)例 2、value實(shí)例變量的偏移量 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í)弥锄,一個(gè)線程修改數(shù)據(jù),可以保證其它線程立馬看到修改后的值
CAS 保證數(shù)據(jù)更新的原子性蟆沫。

Unsafe源碼解析

下面分析下Unsafe 類中的實(shí)現(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)绸狐,原子操作其實(shí)只支持下面三個(gè)方法。

  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 實(shí)現(xiàn)方式一樣,只不過是把Boolean轉(zhuǎn)換成 0和1進(jìn)行操作拆座。

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

CAS缺點(diǎn)

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末萤厅,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子衙伶,更是在濱河造成了極大的恐慌,老刑警劉巖害碾,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矢劲,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡慌随,警方通過查閱死者的電腦和手機(jī)芬沉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來阁猜,“玉大人丸逸,你說我怎么就攤上這事√昱郏” “怎么了黄刚?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長民效。 經(jīng)常有香客問我憔维,道長涛救,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任业扒,我火速辦了婚禮检吆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘程储。我一直安慰自己蹭沛,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布章鲤。 她就那樣靜靜地躺著摊灭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪咏窿。 梳的紋絲不亂的頭發(fā)上斟或,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音集嵌,去河邊找鬼萝挤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛根欧,可吹牛的內(nèi)容都是我干的怜珍。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼凤粗,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼酥泛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嫌拣,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤柔袁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后异逐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捶索,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年灰瞻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腥例。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡酝润,死狀恐怖燎竖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情要销,我是刑警寧澤构回,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響捐凭,放射性物質(zhì)發(fā)生泄漏拨扶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一茁肠、第九天 我趴在偏房一處隱蔽的房頂上張望患民。 院中可真熱鬧垦梆,春花似錦、人聲如沸印蓖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽厅各。三九已至队塘,卻和暖如春憔古,著一層夾襖步出監(jiān)牢的瞬間鸿市,已是汗流浹背适贸。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蕊肥,地道東北人壁却。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓赔硫,卻偏偏與公主長得像爪膊,于是被迫代替她去往敵國和親推盛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子耘成,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

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

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