Java并發(fā)那些事兒-CAS

CAS(Compare And Swap)比較與交換:一種無(wú)鎖算法荔睹。在不使用鎖(沒(méi)有線程被阻塞)的情況下實(shí)現(xiàn)多線程之間的變量同步浆熔。java.util.concurrent包中的原子類就是通過(guò)CAS來(lái)實(shí)現(xiàn)了樂(lè)觀鎖组哩。

CAS算法涉及到三個(gè)操作數(shù):1特漩,需要讀寫的內(nèi)存值V纤怒;2,舊的預(yù)期值A(chǔ);3克滴,要寫入的新值B逼争。

當(dāng)且僅當(dāng)V的值等于A時(shí),CAS通過(guò)原子方式用新值B來(lái)更新V的值(“比較+更新”整體是一個(gè)原子操作)劝赔,否則不會(huì)執(zhí)行任何操作誓焦。一般情況下,“更新”是一個(gè)不斷重試的操作着帽。

通過(guò)查看AtomicInteger源碼來(lái)看看具體的CAS算法杂伟。

AtomicInteger類定義

unsafe:獲取并操作內(nèi)存的數(shù)據(jù)。

valueOffset:存儲(chǔ)value在AtomicInteger中的偏移量仍翰。

value:存儲(chǔ)AtomicInteger的int值赫粥,該屬性借助volatile關(guān)鍵字保證其在線程間是可見(jiàn)的。

看看AtomicInteger中自增方法incrementAndGet()

AtomicInteger.incrementAndGet()
Unsafe.class:getAndAddInt()
OpenJDK 8中的Unsafe.java類

compareAndSwapInt中使用CAS來(lái)更新值歉备,如果更新失敗就重新獲取內(nèi)存中的值傅是,再次調(diào)用compareAndSwapInt方法更新值,直到更新成功蕾羊。在getAndAddInt方法中采用了自旋方式等待鎖釋放。

自旋方式等待鎖釋放

CAS通過(guò)調(diào)用JNI(Java native interface:Java本地調(diào)用)的代碼實(shí)現(xiàn)帽驯,允許Java代碼調(diào)用其他語(yǔ)言龟再。compareAndSwapInt()是本地方法調(diào)用CPU底層指令來(lái)實(shí)現(xiàn)(通過(guò)CPU的cmpxchg指令:作用是將指定內(nèi)存地址的內(nèi)容與所給的某個(gè)值相比,如果相等尼变,則將其內(nèi)容替換為指令中提供的新值利凑,如果不相等,則更新失斚邮酢)哀澈。

其實(shí)要保持?jǐn)?shù)據(jù)的一致性,都需要加鎖度气,唯一的區(qū)別就是在哪里加鎖割按,加什么鎖。本地方法調(diào)用CPU底層來(lái)實(shí)現(xiàn)的CAS磷籍,最終也是通過(guò)加鎖來(lái)完成的适荣,這里的加鎖就是在CPU上面了。

CPU的鎖:

1院领,處理器自動(dòng)保證基本內(nèi)存操作的原子性:當(dāng)一個(gè)處理器讀取一個(gè)字節(jié)時(shí)弛矛,其他處理器不能訪問(wèn)這個(gè)字節(jié)的內(nèi)存地址。這個(gè)自動(dòng)保證是在單處理器對(duì)同一個(gè)緩存行里進(jìn)行的操作是原子的比然,但是復(fù)雜的內(nèi)存操作處理不能自動(dòng)保證其原子性(跨總線寬度丈氓,跨多個(gè)緩存行,跨頁(yè)表的訪問(wèn))。在這種情況下處理器提供總線鎖定和緩存鎖定來(lái)保證復(fù)雜內(nèi)存操作的原子性万俗。

2湾笛,總線鎖保證其原子性:如果多個(gè)處理器同時(shí)對(duì)共享變量進(jìn)行讀改寫操作。那么共享變量就會(huì)被多個(gè)處理器同時(shí)進(jìn)行操作该编,這樣讀改寫操作就不是原子的迄本,操作完之后共享變量的值會(huì)和期望的不一致。例如i++操作课竣,多個(gè)處理器同時(shí)從各自的緩存中讀取變量i嘉赎,分別進(jìn)行加一操作,然后分別寫入系統(tǒng)內(nèi)存當(dāng)中于樟。那么想要保證讀改寫共享變量的操作是原子的公条,就必須保證一個(gè)CPU讀改寫共享變量的時(shí)候,其他CPU不能操作緩存了該共享變量?jī)?nèi)存地址的緩存迂曲。

總線鎖可以解決這個(gè)問(wèn)題靶橱,總線鎖就是使用處理器提供的一個(gè)LOCK#信號(hào),當(dāng)一個(gè)處理器在總線上輸出此信號(hào)時(shí)路捧,其他處理器的請(qǐng)求將被阻塞住,那么該處理器可以獨(dú)占使用共享內(nèi)存(其他處理器暫時(shí)無(wú)法通過(guò)總線訪問(wèn)內(nèi)存)关霸。總線鎖把CPU和內(nèi)存之間的通信鎖住了杰扫,這使得鎖定期間队寇,其他處理器不能操作其他內(nèi)存地址的數(shù)據(jù),這個(gè)開(kāi)銷比較大章姓,往往也不需要佳遣。其實(shí)大多數(shù)的時(shí)候只需要鎖定和處理數(shù)據(jù)相關(guān)的緩存的內(nèi)存地址就行了。

3凡伊,緩存鎖保證原子性:在同一時(shí)刻我們只需保證對(duì)某個(gè)內(nèi)存地址的操作是原子性零渐,所以出現(xiàn)了緩存鎖。頻繁使用的內(nèi)存會(huì)緩存在處理器高速緩存里系忙。那么原子操作就可以直接在處理器內(nèi)部緩存中進(jìn)行诵盼,并不需要進(jìn)行總線鎖。緩存鎖定:如果緩存在處理器緩存行中的內(nèi)存區(qū)域在LOCK操作期間被鎖定笨觅,當(dāng)它執(zhí)行鎖操作回寫內(nèi)存時(shí)拦耐,處理器不在總線上聲明LOCK#信號(hào),而是修改內(nèi)部的內(nèi)存地址见剩。并允許它的緩存一致性機(jī)制來(lái)保證操作的原子性杀糯,因?yàn)榫彺嬉恢滦詸C(jī)制會(huì)阻止同時(shí)修改被兩個(gè)以上處理器緩存的內(nèi)存區(qū)域數(shù)據(jù),當(dāng)其他處理器回寫已被鎖定的緩存行的數(shù)據(jù)時(shí)會(huì)起緩存行無(wú)效(緩存一致性機(jī)制:是當(dāng)某塊CPU對(duì)緩存中的數(shù)據(jù)進(jìn)行操作了之后苍苞,就通知其他CPU放棄儲(chǔ)存在它們內(nèi)部的緩存固翰,或者從主內(nèi)存中重新讀壤俏场)。參考:MESI協(xié)議

例外:在處理器支持緩存鎖的情況下骂际,以下情況處理器不會(huì)使用緩存鎖定:1疗琉,當(dāng)操作的數(shù)據(jù)不能被緩存在處理器內(nèi)部或者操作的數(shù)據(jù)跨多個(gè)緩存行,則處理器調(diào)用總線鎖歉铝。

疑問(wèn):在CPU層面的緩存一致性機(jī)制已經(jīng)保持了共享變量的一致性問(wèn)題盈简,那么在代碼層面上的多線程代碼為什么還需要同步?

剛剛網(wǎng)上看到了一種說(shuō)法:有了緩存一致性協(xié)議為什么還需要多線程同步太示? - 知乎

CAS的缺點(diǎn):

1柠贤,ABA問(wèn)題:因?yàn)镃AS需要在操作值的時(shí)候檢查下值有沒(méi)有發(fā)生變化,如果沒(méi)有發(fā)生變化則更新类缤,但是如果一個(gè)值原來(lái)是A臼勉,變成了B,又變成了A餐弱,那么使用CAS進(jìn)行檢查時(shí)會(huì)發(fā)現(xiàn)它的值沒(méi)有發(fā)生變化宴霸,但是實(shí)際上卻變化了。ABA問(wèn)題的解決思路就是使用版本號(hào)膏蚓。在變量前面追加上版本號(hào)瓢谢,每次變量更新的時(shí)候把版本號(hào)加一,那么A-B-A 就會(huì)變成1A-2B-3A驮瞧。

2恩闻,循環(huán)時(shí)間長(zhǎng)開(kāi)銷大:自旋CAS如果長(zhǎng)時(shí)間不成功,會(huì)給CPU帶來(lái)非常大的執(zhí)行開(kāi)銷剧董。

3,只能保證一個(gè)共享變量的原子操作:當(dāng)對(duì)一個(gè)共享變量執(zhí)行操作時(shí)破停,我們可以使用循環(huán)CAS的方式來(lái)保證原子操作翅楼,但是對(duì)多個(gè)共享變量操作時(shí),循環(huán)CAS就無(wú)法保證操作的原子性真慢,這個(gè)時(shí)候就可以用鎖毅臊,或者有一個(gè)取巧的辦法,就是把多個(gè)共享變量合并成一個(gè)共享變量來(lái)操作黑界。比如有兩個(gè)共享變量i=2,j=a管嬉,合并一下ij=2a,然后用CAS來(lái)操作ij朗鸠。從Java1.5開(kāi)始JDK提供了AtomicReference類來(lái)保證引用對(duì)象之間的原子性蚯撩,你可以把多個(gè)變量放在一個(gè)對(duì)象里來(lái)進(jìn)行CAS操作。

參考:

JAVA CAS原理深度分析 - - ITeye博客

Java的多線程機(jī)制系列:(二)緩存一致性和CAS - 孟衡 - 博客園

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末烛占,一起剝皮案震驚了整個(gè)濱河市胎挎,隨后出現(xiàn)的幾起案子沟启,更是在濱河造成了極大的恐慌,老刑警劉巖犹菇,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件德迹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡揭芍,警方通過(guò)查閱死者的電腦和手機(jī)胳搞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)称杨,“玉大人肌毅,你說(shuō)我怎么就攤上這事×辛恚” “怎么了芽腾?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)页衙。 經(jīng)常有香客問(wèn)我摊滔,道長(zhǎng),這世上最難降的妖魔是什么店乐? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任艰躺,我火速辦了婚禮,結(jié)果婚禮上眨八,老公的妹妹穿的比我還像新娘腺兴。我一直安慰自己,他們只是感情好廉侧,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布页响。 她就那樣靜靜地躺著,像睡著了一般段誊。 火紅的嫁衣襯著肌膚如雪闰蚕。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,772評(píng)論 1 290
  • 那天连舍,我揣著相機(jī)與錄音没陡,去河邊找鬼。 笑死索赏,一個(gè)胖子當(dāng)著我的面吹牛盼玄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播潜腻,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼埃儿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了砾赔?” 一聲冷哼從身側(cè)響起蝌箍,我...
    開(kāi)封第一講書(shū)人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤青灼,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后妓盲,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體杂拨,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年悯衬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了弹沽。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡筋粗,死狀恐怖策橘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情娜亿,我是刑警寧澤丽已,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站买决,受9級(jí)特大地震影響沛婴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜督赤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一嘁灯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧躲舌,春花似錦丑婿、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至约计,卻和暖如春尘奏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背病蛉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瑰煎,地道東北人铺然。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像酒甸,于是被迫代替她去往敵國(guó)和親魄健。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

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

  • Java8張圖 11插勤、字符串不變性 12沽瘦、equals()方法革骨、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,696評(píng)論 0 11
  • 第2章 java并發(fā)機(jī)制的底層實(shí)現(xiàn)原理 Java中所使用的并發(fā)機(jī)制依賴于JVM的實(shí)現(xiàn)和CPU的指令析恋。 2.1 vo...
    kennethan閱讀 1,403評(píng)論 0 2
  • 在JDK 5之前Java語(yǔ)言是靠synchronized關(guān)鍵字保證同步的良哲,這會(huì)導(dǎo)致有鎖 鎖機(jī)制存在以下問(wèn)題: (1...
    SunnyMore閱讀 5,007評(píng)論 2 18
  • CAS簡(jiǎn)歷 CAS(Compare and swap)比較和替換是設(shè)計(jì)并發(fā)算法時(shí)用到的一種技術(shù) 。Compare ...
    classtag閱讀 4,146評(píng)論 2 37
  • java.util.concurrent包完全建立在CAS之上助隧。 AQS筑凫,非阻塞數(shù)據(jù)結(jié)構(gòu)和原子變量類(java.u...
    光劍書(shū)架上的書(shū)閱讀 5,604評(píng)論 0 8