CAS的一些研究與個(gè)人思考

cas技術(shù)守问,或者說設(shè)計(jì)思想,其實(shí)挺讓人著迷的体啰,至少對(duì)于我來說是如此荒勇,這些天一直在研究這一塊窿凤,今天寫寫針對(duì)cas的總結(jié),會(huì)持續(xù)更新品姓;

cas(compare and swap)植酥,中文翻譯比較替換,大家又將它談為樂觀鎖,說到鎖赏酥,關(guān)于java里面太多了搬素,而且還是面試官特別喜歡問的,簡(jiǎn)直讓人頭皮發(fā)麻猪杭,和樂觀鎖比較最多的就是悲觀鎖(synchronized)了。除此之外還有偏向鎖需纳,輕量級(jí)鎖口蝠,重量級(jí)鎖眉反,自旋鎖播歼,類鎖雇初,數(shù)據(jù)庫里面還有表鎖刊橘,行鎖嘴纺,elasticsearch里面還有全局鎖闲擦,文檔鎖俺榆,樹鎖萍桌,redis中的分布式鎖等等寇损;

我們平時(shí)應(yīng)用多線程技術(shù)非常多矛市,比如網(wǎng)上商店賣一類商品,多個(gè)用戶同時(shí)去購買,列車賣票着憨,多個(gè)用戶同時(shí)購買同一車次的車票等等場(chǎng)景,這些都涉及到了一個(gè)問題惧眠,共享資源暮顺,在多個(gè)線程去訪問共享資源的時(shí)候或链,如何去保證共享資源的一致性?在操作系統(tǒng)里面有一個(gè)臨界區(qū)的概念筛婉,其實(shí)就是一個(gè)程序片段,這個(gè)片段在同一時(shí)刻挑庶,針對(duì)共享資源,只能一個(gè)線程去訪問率挣,這就是鎖旱眯。那么這個(gè)程序片段。對(duì)應(yīng)java里面是什么呢惨奕?是synchronized雹洗,被synchronized修飾過的程序片段,相當(dāng)于加了鎖覆醇,而這個(gè)鎖鞋仍,就是悲觀鎖肚豺。那么問題來了蛉威,如果一個(gè)系統(tǒng)中的共享資源是改多讀少的情況敢朱,加鎖為了保證數(shù)據(jù)的一致性無可厚非构灸,但是如果這個(gè)系統(tǒng)中的共享資源是讀多寫少的情況呢?要知道曹阔,無論是讀半开,還是寫,進(jìn)入該程序片段赃份,都是要加鎖的寂拆,而每加鎖一次這涉及到了系統(tǒng)調(diào)用內(nèi)核態(tài)和用戶態(tài)之間的切換,頻繁加鎖抓韩,釋放鎖纠永,這個(gè)代價(jià)是非常大的,所以我們提出了樂觀鎖的概念谒拴,樂觀鎖在應(yīng)用層尝江,其實(shí)是沒有鎖的。這也是CAS技術(shù)為什么多用于讀多寫少的應(yīng)用場(chǎng)景英上。

CAS是怎么做到?jīng)]有加鎖就能保證數(shù)據(jù)的一致性的呢茂装?

首先怠蹂,CAS有三個(gè)概念,分別是V少态,A,B易遣;V表示要修改的值彼妻,即共享資源,這個(gè)值可以是內(nèi)存中豆茫,可以是數(shù)據(jù)庫中侨歉,總之這個(gè)是你的共享資源,A表示更新的比較預(yù)期值揩魂,B表示即將要更新的新值幽邓。這么說可能有點(diǎn)不太理解,那么說說cas的操作流程就明白了火脉。流程:V為共享資源牵舵,多個(gè)線程要去操作V,每個(gè)線程會(huì)先讀取V的值倦挂,將值賦給A畸颅,其中一個(gè)線程要執(zhí)行更新操作,這個(gè)時(shí)候方援,會(huì)將這個(gè)線程的A值和現(xiàn)在內(nèi)存中的V值對(duì)比没炒,如果沒有變化,說明這個(gè)之前沒有別的線程修改(這里不涉及ABA問題犯戏,ABA問題可以通過時(shí)間戳或者版本解決)送火,于是將V的值更新為B,如果A值和V值不相等先匪,則不更新或者重新獲取A值种吸。

這就是CAS的設(shè)計(jì),不過胚鸯,看到這里骨稿,大家有沒有想過,如果有兩個(gè)或者多個(gè)線程同時(shí)都經(jīng)過了第一次的V和A值比較呢姜钳?那么到了更新一步是不是會(huì)出現(xiàn)數(shù)據(jù)不一致的情況坦冠?因?yàn)楸容^和更新其實(shí)并不是原子操作。

我們來討論一下JAVA 8中是怎么解決這個(gè)問題的哥桥。

比較典型的原子操作對(duì)象辙浑,AtomicInteger,我們可以看一下里面的源碼拟糕,自增1是怎么實(shí)現(xiàn)的

public final int incrementAndGet() {

? ? for (;;) {

? ? ? ? int current = get();

? ? ? ? int next = current + 1;

? ? ? ? if (compareAndSet(current, next))

? ? ? ? ? ? return next;

? ? }

}

可以看到判呕,首先get()從內(nèi)存中獲取內(nèi)存值倦踢,在使用之前,你要自己聲明一個(gè)volatile的所有線程可見變量侠草。接著辱挥,加1賦值給了next,接著將current和next傳給了compareAndSet函數(shù)边涕,如果成功晤碘,則返回next值,這個(gè)時(shí)候功蜓,current和next就是分別對(duì)應(yīng)上文的A园爷,B。繼續(xù)看compareAndSet是怎么保證原子操作的式撼。

public final boolean compareAndSet(int expect, int update) {?

? ? return unsafe.compareAndSwapInt(this, valueOffset, expect, update);

? ? }

可以看到童社,這里直接把值傳給了unsafe的compareAndSwapInt方法,由它來保證原子操作著隆,這里我就不貼源碼分析了扰楼。我直接總結(jié),這個(gè)unsafe其實(shí)是java中的jni(java native interface)技術(shù)調(diào)用另外語言的實(shí)現(xiàn)旅东,這里是*.cpp灭抑,名字太長,我用*代替抵代,這個(gè)操作呢其實(shí)是硬件層面的技術(shù)腾节,涉及到底層的匯編,總的來說就是進(jìn)入這個(gè)unsafe的函數(shù)后荤牍,在計(jì)算機(jī)內(nèi)存運(yùn)行中案腺,同一時(shí)刻通過總線和緩存的鎖定,從而實(shí)現(xiàn)了比較和替換康吵,達(dá)到原子操作的目的劈榨,不得不說,看*.cpp文件是一件掉發(fā)的操作晦嵌,珍愛發(fā)型同辣,遠(yuǎn)離禿頭;

說了半天惭载,原來這里面最難的原子操作是在硬件層面呀旱函,那我們有沒有可能在應(yīng)用層就能實(shí)現(xiàn)呢?答案自然是有的描滔,這里我提供一個(gè)我自己的個(gè)人想法棒妨;

這里我采用的做法是調(diào)用已經(jīng)寫好的AtomicInteger來輔助。

1含长、聲明一個(gè)針對(duì)該共享資源的volatile int變量value券腔,初始化為0伏穆;

2、兩個(gè)或者多個(gè)線程進(jìn)入了第一步的V和A比較纷纫,獲取value值枕扫,這個(gè)時(shí)候,多個(gè)線程就變成了串行化涛酗,而不用加鎖铡原,因?yàn)関alue是嚴(yán)格排序的;

3商叹、獲得value為1的線程執(zhí)行更新操作,操作完之后只泼,初始化為0剖笙,其余不為了1的線程重新獲取V值或者處理丟棄;

那么還有一個(gè)問題请唱,如果獲取到1的線程在執(zhí)行過程中死掉了怎么辦弥咪?如果死掉了,那么豈不是這里一直就停住了十绑,不能操作共享資源啦聚至?因?yàn)橹挥衯alue為1的線程才能進(jìn)入臨界區(qū),所以本橙,在這里扳躬,我們還要設(shè)置一個(gè)超時(shí)時(shí)間,如果超過設(shè)定時(shí)間甚亭,還是要?dú)⒌衄F(xiàn)在操作資源的線程贷币,重置value值為0和復(fù)原在此過程中改變的V值,以便讓其他線程進(jìn)入亏狰,當(dāng)然這種情況幾乎很少發(fā)生役纹,但還是得考慮。

ABA問題就是線程1的A值去比較V值暇唾,發(fā)現(xiàn)相等促脉,但是如果在這之前,線程2將V值改了策州,線程3又將V值改回來瘸味,那這樣其實(shí)A值和V值是相等的,但其實(shí)里面已經(jīng)有了變動(dòng)抽活,所以叫ABA問題硫戈,可以采用時(shí)間戳,在對(duì)比那一步下硕,加入了時(shí)間戳對(duì)比就可以了,或者如果是elasticsearch中可以采用版本號(hào)比較。

cas應(yīng)用層實(shí)現(xiàn)做法分唾,大家也可以去看看disruptor框架的做法沧踏,里面提出了一個(gè)前門后門的概念,采用環(huán)形緩存做法洗搂,據(jù)說非常高效;

如果還有別的地方?jīng)]有考慮到,歡迎大家提出铸题,一起學(xué)習(xí)進(jìn)步。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末琢感,一起剝皮案震驚了整個(gè)濱河市丢间,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌驹针,老刑警劉巖烘挫,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異柬甥,居然都是意外死亡饮六,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門苛蒲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來卤橄,“玉大人,你說我怎么就攤上這事臂外】咂耍” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵寄月,是天一觀的道長辜膝。 經(jīng)常有香客問我,道長漾肮,這世上最難降的妖魔是什么厂抖? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮克懊,結(jié)果婚禮上忱辅,老公的妹妹穿的比我還像新娘。我一直安慰自己谭溉,他們只是感情好墙懂,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著扮念,像睡著了一般损搬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天巧勤,我揣著相機(jī)與錄音嵌灰,去河邊找鬼。 笑死颅悉,一個(gè)胖子當(dāng)著我的面吹牛沽瞭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播剩瓶,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼驹溃,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了延曙?” 一聲冷哼從身側(cè)響起豌鹤,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎枝缔,沒想到半個(gè)月后傍药,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡魂仍,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拣挪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片擦酌。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖菠劝,靈堂內(nèi)的尸體忽然破棺而出赊舶,到底是詐尸還是另有隱情,我是刑警寧澤赶诊,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布笼平,位于F島的核電站,受9級(jí)特大地震影響舔痪,放射性物質(zhì)發(fā)生泄漏寓调。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一锄码、第九天 我趴在偏房一處隱蔽的房頂上張望夺英。 院中可真熱鬧,春花似錦滋捶、人聲如沸痛悯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽载萌。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間扭仁,已是汗流浹背垮衷。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留斋枢,地道東北人帘靡。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像瓤帚,于是被迫代替她去往敵國和親描姚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

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