原子操作的實(shí)現(xiàn)原理及CAS分析

1.原子操作意為“不可被中斷的一個(gè)或一系列操作”樟插。再多處理器上實(shí)現(xiàn)原子操作就變的有點(diǎn)復(fù)雜绅作。


2.處理器如何實(shí)現(xiàn)原子操作郊楣?

? ? ? ?首先處理器能自動(dòng)保證基本的內(nèi)存操作是原子性的。表示當(dāng)一個(gè)處理器讀取一個(gè)字節(jié)時(shí)烁挟,其他處理器不能訪問(wèn)這個(gè)字節(jié)的內(nèi)存地址。但是對(duì)于復(fù)雜的內(nèi)存操作處理器是不能自動(dòng)保證其原子性的骨坑,比如跨總線寬度撼嗓、跨多個(gè)緩存行和跨頁(yè)表的訪問(wèn)。但是欢唾,處理器提供了總線鎖定緩存鎖定兩個(gè)機(jī)制來(lái)保證復(fù)雜內(nèi)存操作的原子性且警。


3.總線鎖定和緩存鎖定

? ? ?-1)使用總線鎖保證原子性

? ? ? 第一機(jī)制是通過(guò)總線鎖保證原子性。如果多個(gè)處理器同時(shí)對(duì)共享變量進(jìn)行讀改寫(xiě)操作(i++就是經(jīng)典的讀改寫(xiě)操作)礁遣,那么共享變量就會(huì)被多個(gè)處理器同時(shí)進(jìn)行操作斑芜,這樣讀改寫(xiě)操作就不是原子的,操作完之后的共享變量的值會(huì)和期望的不一致祟霍。舉個(gè)例子杏头,如果i=1,我們進(jìn)行兩次i++操作沸呐,我們期望的結(jié)果是3醇王,但是有可能結(jié)果是2。? ?原因可能是多個(gè)處理器同時(shí)從各自的緩存中讀取變量i垂谢,分別進(jìn)行加1操作厦画,然后分別寫(xiě)入系統(tǒng)內(nèi)存中。那么滥朱,想要保證讀改寫(xiě)共享變量的操作是原子的根暑,就必須保證CPU1讀改寫(xiě)共享變量的時(shí)候,CPU2不能操作緩存了該共享變量?jī)?nèi)存地址的緩存徙邻。

? ? ? ?處理器使用總線鎖就是來(lái)解決這個(gè)問(wèn)題的排嫌。所謂總線鎖就是使用處理器提供的一個(gè)LOCLK#信號(hào),當(dāng)一個(gè)處理器在總線上輸出此信號(hào)時(shí)缰犁,其他處理器的請(qǐng)求將被阻塞住淳地,那么該處理器可以獨(dú)占共享內(nèi)存。

? ? ?-2)使用緩存鎖保證原子性

? ? ? 第二機(jī)制是通過(guò)緩存鎖定來(lái)保證原子性帅容。在同一時(shí)刻颇象,只需保證對(duì)某個(gè)內(nèi)存地址的操作是原子性即可,但總線鎖定把CPU和內(nèi)存之間的通信鎖住了并徘,這使得鎖定期間遣钳,其它處理器不能操作其他內(nèi)存地址的數(shù)據(jù),所以總線鎖定的開(kāi)銷(xiāo)比較大麦乞,目前處理器在某些場(chǎng)合下使用緩存鎖定代替總線鎖定來(lái)進(jìn)行優(yōu)化蕴茴。頻繁使用的內(nèi)存會(huì)緩存在處理器的L1劝评、L2和L3高速緩存里,那么原子操作就可以直接在處理器內(nèi)部緩存中進(jìn)行倦淀,并不需要聲明總線鎖蒋畜,在Pentium6和目前的處理器中可以使用“緩存鎖定”的方式來(lái)實(shí)現(xiàn)復(fù)雜的原子性。

? ? ?所謂“緩存鎖定”是指如果共享內(nèi)存如果被換存在處理器的緩存行中撞叽,并且在Lock操作期間被鎖定姻成,那么當(dāng)它執(zhí)行鎖操作回寫(xiě)到內(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)其他處理器回寫(xiě)已被鎖定的緩存行的數(shù)據(jù)時(shí)辛润,會(huì)使緩存行無(wú)效。

? ? ?但是有兩種情況下處理器不會(huì)使用緩存鎖定

? ? ?第一種情況是:當(dāng)操作的數(shù)據(jù)不能被緩存在處理器內(nèi)部见秤,或操作的數(shù)據(jù)跨多個(gè)緩存行時(shí)砂竖,則處理器會(huì)調(diào)用總線鎖定。

? ? ?第二種情況是:有些處理器不支持緩存鎖定鹃答。對(duì)于Intel486和Pentium處理器乎澄,就算鎖定的內(nèi)存區(qū)域在處理器的緩存行中也會(huì)調(diào)用總線鎖定。

? ? ?針對(duì)以上兩個(gè)機(jī)制测摔,我們通過(guò)Intel處理器提供了很多Lock前綴的指令來(lái)實(shí)現(xiàn)置济。例如,位測(cè)試和修改指令:BTS锋八、BTR浙于、BTC;交換指令XADD挟纱、CMPXCHG羞酗,以及其他一些操作數(shù)和邏輯指令。被這些指令操作的內(nèi)存區(qū)域就會(huì)加鎖紊服,導(dǎo)致其他處理器不能同時(shí)訪問(wèn)它檀轨。


4.CAS

在Java中可以通過(guò)鎖和循環(huán)CAS的方式來(lái)實(shí)現(xiàn)原子操作。

? (1)使用循環(huán)CAS實(shí)現(xiàn)原子操作

? ? 使用CAS操作可以不加鎖的實(shí)現(xiàn)原子操作欺嗤,如i++操作在多線程下参萄,i處于一種不穩(wěn)定的狀態(tài)。使用鎖可以解決同步問(wèn)題煎饼,但是也可以使用CAS操作也可以實(shí)現(xiàn)操作的原子性拧揽。代碼如下

intexpect = a;

if(a.compareAndSet(expect,a+1)) {?

?????????doSomeThing1();

}else{

?doSomeThing2();

}

? ?這樣如果a的值被改變了a++就不會(huì)被執(zhí)行。按照上面的寫(xiě)法,a!=expect之后,a++就不會(huì)被執(zhí)行淤袜,如果我們還是想執(zhí)行a++操作怎么辦,沒(méi)關(guān)系衰伯,可以采用while循環(huán)

while(true) {

????intexpect = a;

????if(a.compareAndSet(expect, a +1)) {

?????????doSomeThing1();

????????return;?

?????}else{?

?????????doSomeThing2();?

?????}

}

? ?采用上面的寫(xiě)法铡羡,在沒(méi)有鎖的情況下實(shí)現(xiàn)了a++操作,這實(shí)際上是一種非阻塞算法意鲸。

? ?CAS有3個(gè)操作數(shù)烦周,內(nèi)存值V,舊的預(yù)期值A(chǔ)怎顾,要修改的新值B读慎。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),將內(nèi)存值V修改為B槐雾,否則什么都不做夭委。

? ?成功過(guò)程中需要2個(gè)步驟:比較this == expect,替換this = update募强,compareAndSwapInt如何這兩個(gè)步驟的原子性呢株灸?

? ?JVM中的CAS操作是利用了處理器提供的CMPXCHG指令實(shí)現(xiàn)的。自旋CAS實(shí)現(xiàn)的基本思路就是循環(huán)進(jìn)行CAS操作直到成功為止擎值。

? ?CAS通過(guò)調(diào)用JNI的代碼實(shí)現(xiàn)的慌烧。JNI:Java Native Interface為JAVA本地調(diào)用,允許java調(diào)用其他語(yǔ)言鸠儿。而compareAndSwapInt就是借助C來(lái)調(diào)用CPU底層指令實(shí)現(xiàn)的屹蚊。程序會(huì)根據(jù)當(dāng)前處理器的類(lèi)型來(lái)決定是否為cmpxchg指令添加lock前綴。如果程序是在多處理器上運(yùn)行进每,就為cmpxchg指令加上lock前綴(lock cmpxchg)汹粤。反之,如果程序是在單處理器上運(yùn)行品追,就省略lock前綴(單處理器自身會(huì)維護(hù)單處理器內(nèi)的順序一致性玄括,不需要lock前綴提供的內(nèi)存屏障效果)。


? ? 5.CAS實(shí)現(xiàn)原子操作的三大問(wèn)題

在Java并發(fā)包中有一些并發(fā)框架也使用了自旋CAS的方式來(lái)實(shí)現(xiàn)原子操作肉瓦,比如LinkedTransferQueue類(lèi)的Xfer方法遭京。CAS雖然很高效地解決了原子操作,但是CAS仍然存在三大問(wèn)題泞莉。ABA問(wèn)題哪雕,循環(huán)時(shí)間長(zhǎng)開(kāi)銷(xiāo)大,以及只能保證一個(gè)共享變量的原子操作鲫趁。

? ? ? ?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)加1,那么A->B->A就會(huì)變成1A->2B->3A城菊。從Java1.5開(kāi)始备燃,JDK的Atomic包里提供了一個(gè)類(lèi)AtomicStampedReference來(lái)解決ABA問(wèn)題。這個(gè)類(lèi)的compareAndSet方法的作用是首先檢查當(dāng)前引用是否等于預(yù)期引用凌唬,并且檢查當(dāng)前標(biāo)志是否等于預(yù)期標(biāo)志并齐,如果全部相等,則以原子方式將該飲用和該標(biāo)志的值設(shè)置為給定的更新值法瑟。

public boolean compareAndSet{

? ? ? ? V? ? expectedReference冀膝,? ? ? ? //預(yù)期引用

? ? ? ? V? ? newReference,? ? ? ? ? ? ? ? //更新后的引用

? ? ? ?int? ? expectedStamp? ? ? ? ? ? ? ? ?//預(yù)期標(biāo)志

? ? ? ?int? ? newStamp? ? ? ? ? ? ? ? ? ? ? ? ?//更新后的標(biāo)志

? ? ?2)循環(huán)時(shí)間長(zhǎng)開(kāi)銷(xiāo)大霎挟。自旋CAS如果長(zhǎng)時(shí)間不成功窝剖,會(huì)給CPU帶來(lái)非常大的執(zhí)行開(kāi)銷(xiāo)。如果JVM能支持處理器提供的pause指令酥夭,那么效率會(huì)有一定的提升赐纱。pause指令有兩個(gè)作用:第一,它可以延遲流水線執(zhí)行指令熬北,使CPU不會(huì)消耗過(guò)多的執(zhí)行資源疙描,延遲的時(shí)間取決于具體實(shí)現(xiàn)的版本,在一些處理器上延遲時(shí)間是零讶隐;第二起胰,它可以避免在退出循環(huán)的時(shí)候因內(nèi)存順序沖突而引起CPU流水線被清空,從而提高CPU的執(zhí)行效率巫延。

? ?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=2a疯攒,然后用CAS來(lái)操作ij嗦随。從JDK1.5開(kāi)始提供了AtomicReference類(lèi)來(lái)保證引用對(duì)象之間的原子性,就可以把多個(gè)變量放在一個(gè)對(duì)象里來(lái)進(jìn)行CAS操作敬尺。


? ? ??6.使用鎖機(jī)制實(shí)現(xiàn)原子操作

? ? ?鎖機(jī)制保證了只有獲得鎖的線程才能夠操作鎖定的內(nèi)存區(qū)域。JVM內(nèi)部實(shí)現(xiàn)了很多種鎖機(jī)制贴浙,有偏向鎖砂吞、輕量級(jí)鎖和互斥鎖。有意思的是除了偏向鎖崎溃,JVM實(shí)現(xiàn)鎖的方式都用了循環(huán)CAS蜻直,即當(dāng)一個(gè)線程想進(jìn)入同步塊的時(shí)候使用循環(huán)CAS的方式來(lái)獲取鎖,當(dāng)他退出同步塊的時(shí)候使用循環(huán)CAS釋放鎖袁串。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末概而,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子囱修,更是在濱河造成了極大的恐慌赎瑰,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件破镰,死亡現(xiàn)場(chǎng)離奇詭異餐曼,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)鲜漩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)源譬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人孕似,你說(shuō)我怎么就攤上這事踩娘。” “怎么了喉祭?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵养渴,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我臂拓,道長(zhǎng)厚脉,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任胶惰,我火速辦了婚禮傻工,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己中捆,他們只是感情好鸯匹,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著泄伪,像睡著了一般殴蓬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蟋滴,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天染厅,我揣著相機(jī)與錄音,去河邊找鬼津函。 笑死肖粮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的尔苦。 我是一名探鬼主播涩馆,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼允坚!你這毒婦竟也來(lái)了魂那?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤稠项,失蹤者是張志新(化名)和其女友劉穎涯雅,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體皿渗,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡斩芭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乐疆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片划乖。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖挤土,靈堂內(nèi)的尸體忽然破棺而出琴庵,到底是詐尸還是另有隱情,我是刑警寧澤仰美,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布迷殿,位于F島的核電站,受9級(jí)特大地震影響咖杂,放射性物質(zhì)發(fā)生泄漏庆寺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一诉字、第九天 我趴在偏房一處隱蔽的房頂上張望懦尝。 院中可真熱鬧知纷,春花似錦、人聲如沸陵霉。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)踊挠。三九已至乍桂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間效床,已是汗流浹背睹酌。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留剩檀,地道東北人忍疾。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像谨朝,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子甥绿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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