【Java】Java CAS

CAS

首先昼扛,先說下悲觀鎖和樂觀鎖,悲觀鎖等浊,每次操作都先上鎖腮郊,比如獨(dú)占鎖synchronized就是一種悲觀鎖;樂觀鎖就是每次不加鎖而是假設(shè)沒有沖突而去完成某項(xiàng)操作筹燕,如果因?yàn)闆_突失敗就重試轧飞,直到成功為止。樂觀鎖所用到的機(jī)制就是CAS撒踪。

CAS:compare and swap

CAS包括三個值:內(nèi)存地址过咬、期待原值、新值制妄。即當(dāng)賦值的時候掸绞,首先compare,即對比目前的值和期待原值是否一樣耕捞,如果一樣衔掸,則更新原值,否則俺抽,不更新原值敞映,重新獲取。

看一個例子凌埂。

例子

public class Counter {
    private AtomicInteger mAtomicInteger = new AtomicInteger(0);
    private int i = 0;
    public static void main(String []args) {
        final Counter cas = new Counter();
        List<Thread> ts = new ArrayList<>(600);
    
        for(int j = 0; j < 100; j++) {
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    for(int i = 0; i < 1000; i++) {
                        cas.count();
                        cas.safeCount();
                    }
                }
            });
            ts.add(thread);
        }
    
        for(Thread thread : ts) {
            thread.start();
        }
    
        //等待子線程都執(zhí)行完了之后再打印結(jié)果
        for(Thread thread : ts) {
            try {
                thread.join();
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        System.out.println(cas.i);
        System.out.println(cas.mAtomicInteger.get());
    }

    private void safeCount() {
        for(;;) {
            int i = mAtomicInteger.get();
            boolean suc = mAtomicInteger.compareAndSet(i, ++i);
            if (suc) {
                break;
            }
        }
    }

    private void count() {
        i++;
    }
}

這個是一個計(jì)數(shù)器驱显,期待結(jié)果是100000诗芜,但是瞳抓,如果直接i++會發(fā)現(xiàn),結(jié)果不是100000伏恐,原因比較好理解孩哑,修改值的時候包括三步:
1、讀取i
2翠桦、i = i + 1
3横蜒、把計(jì)算得到的i寫入
這個過程明顯不是原子操作的,所以導(dǎo)致值不對销凑。
再看使用CAS方式來實(shí)現(xiàn)的丛晌,即:

for(;;) {
    int i = mAtomicInteger.get();
    boolean suc = mAtomicInteger.compareAndSet(i, ++i);
    if (suc) {
        break;
    }
}

這個非常有意思,compareAndSet傳入的一個期待原值一個新值斗幼,如果原值和期待原值不一致澎蛛,就返回false,如果一致蜕窿,就修改并返回true谋逻。如果一致不成功就不斷重試呆馁,直到成功。

所以其實(shí)這里是通過循環(huán)CAS來實(shí)現(xiàn)的不加鎖的原子操作毁兆。

ABA問題

CAS操作的是存在著一個經(jīng)典的ABA問題浙滤,這個問題可以從下面的例子引入。

public class ABA {
    private static AtomicInteger atomicInt = new AtomicInteger(100);

    public static void main(String[] args) throws InterruptedException {
        Thread intT1 = new Thread(new Runnable() {
            @Override
            public void run() {
                atomicInt.compareAndSet(100, 101);
                atomicInt.compareAndSet(101, 100);
            }
        });

        Thread intT2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                boolean c3 = atomicInt.compareAndSet(100, 101);
                System.out.println(c3); //true
            }
        });

        intT1.start();
        intT2.start();
        intT1.join();
        intT2.join();
    }
}

打印結(jié)果是true气堕。

這里初始值是100纺腊,線程1把100改成101再改成100,線程2認(rèn)為100就是期待原值茎芭,所以線程2在執(zhí)行的時候摹菠,不知道1已經(jīng)執(zhí)行了。所以返回了true骗爆。但是實(shí)際上2的期待原值100已經(jīng)不是現(xiàn)在的100了次氨。

ABA問題的解決思路就是加上版本號,即從A->B->A變成1A->2B->3A摘投,這樣一來只有版本號和值相等煮寡,才認(rèn)為相等。

從Java1.5開始犀呼,java.util.concurrent.atomic包里面引入了帶標(biāo)記的AtomicStampedReference類解決了這個問題幸撕。

還是先看這個例子的更新版。

public class ABA {
    private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<Integer>(100, 0);

    public static void main(String[] args) throws InterruptedException {
        Thread refT1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
                atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
            }
        });

        Thread refT2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int stamp = atomicStampedRef.getStamp();
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
                System.out.println(c3); //false
            }
        });

        refT1.start();
        refT2.start();
    }
}

打印結(jié)果是false外臂。
這里compareAndSet方法做了兩件事坐儿,一個是先檢查當(dāng)前引用是否等于預(yù)期引用,并且檢查當(dāng)前版本號是否等于預(yù)期版本號宋光,如果相等則進(jìn)行更新貌矿。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市罪佳,隨后出現(xiàn)的幾起案子逛漫,更是在濱河造成了極大的恐慌,老刑警劉巖赘艳,帶你破解...
    沈念sama閱讀 211,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件酌毡,死亡現(xiàn)場離奇詭異,居然都是意外死亡蕾管,警方通過查閱死者的電腦和手機(jī)枷踏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掰曾,“玉大人旭蠕,你說我怎么就攤上這事。” “怎么了下梢?”我有些...
    開封第一講書人閱讀 157,435評論 0 348
  • 文/不壞的土叔 我叫張陵客蹋,是天一觀的道長。 經(jīng)常有香客問我孽江,道長讶坯,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,509評論 1 284
  • 正文 為了忘掉前任岗屏,我火速辦了婚禮辆琅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘这刷。我一直安慰自己婉烟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評論 6 386
  • 文/花漫 我一把揭開白布暇屋。 她就那樣靜靜地躺著喝检,像睡著了一般栽烂。 火紅的嫁衣襯著肌膚如雪阎曹。 梳的紋絲不亂的頭發(fā)上度帮,一...
    開封第一講書人閱讀 49,837評論 1 290
  • 那天,我揣著相機(jī)與錄音定鸟,去河邊找鬼而涉。 笑死,一個胖子當(dāng)著我的面吹牛联予,可吹牛的內(nèi)容都是我干的啼县。 我是一名探鬼主播,決...
    沈念sama閱讀 38,987評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼沸久,長吁一口氣:“原來是場噩夢啊……” “哼季眷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起麦向,我...
    開封第一講書人閱讀 37,730評論 0 267
  • 序言:老撾萬榮一對情侶失蹤瘟裸,失蹤者是張志新(化名)和其女友劉穎客叉,沒想到半個月后诵竭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,194評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡兼搏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評論 2 327
  • 正文 我和宋清朗相戀三年卵慰,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片佛呻。...
    茶點(diǎn)故事閱讀 38,664評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡裳朋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出吓著,到底是詐尸還是另有隱情鲤嫡,我是刑警寧澤送挑,帶...
    沈念sama閱讀 34,334評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站暖眼,受9級特大地震影響惕耕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜诫肠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評論 3 313
  • 文/蒙蒙 一司澎、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧栋豫,春花似錦挤安、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至丛肢,卻和暖如春昂羡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背摔踱。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評論 1 266
  • 我被黑心中介騙來泰國打工虐先, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人派敷。 一個月前我還...
    沈念sama閱讀 46,389評論 2 360
  • 正文 我出身青樓蛹批,卻偏偏與公主長得像,于是被迫代替她去往敵國和親篮愉。 傳聞我的和親對象是個殘疾皇子腐芍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評論 2 349

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