目前在Java中存在兩種鎖機(jī)制:synchronized和Lock盐茎,Lock接口及其實(shí)現(xiàn)類是JDK5增加的內(nèi)容。本文并不比較synchronized與Lock孰優(yōu)孰劣,只是介紹二者的實(shí)現(xiàn)原理。
13、偏向鎖和輕量級(jí)鎖澜术、鎖粗化、鎖消除猬腰、鎖膨脹
因?yàn)檫@幾個(gè)概念連續(xù)非常緊密所以放在一起會(huì)方便理解記憶鸟废。
在jdk1.6中對(duì)鎖的實(shí)現(xiàn)引入了大量的優(yōu)化,如鎖粗化(Lock Coarsening)姑荷、鎖消除(Lock Elimination)盒延、輕量級(jí)鎖(Lightweight Locking)、
偏向鎖(Biased Locking)鼠冕、適應(yīng)性自旋(Adaptive Spinning)等技術(shù)來(lái)減少鎖操作的開(kāi)銷(xiāo)添寺。
鎖粗化(Lock Coarsening):也就是減少不必要的緊連在一起的unlock,lock操作懈费,將多個(gè)連續(xù)的鎖擴(kuò)展成一個(gè)范圍更大的鎖计露。
鎖消除(Lock Elimination):通過(guò)運(yùn)行時(shí)JIT編譯器的逃逸分析來(lái)消除一些沒(méi)有在當(dāng)前同步塊以外被其他線程共享的數(shù)據(jù)的鎖保護(hù),
通過(guò)逃逸分析也可以在線程本地Stack上進(jìn)行對(duì)象空間的分配(同時(shí)還可以減少Heap上的垃圾收集開(kāi)銷(xiāo))憎乙。
輕量級(jí)鎖(Lightweight Locking):這種鎖實(shí)現(xiàn)的背后基于這樣一種假設(shè)票罐,即在真實(shí)的情況下我們程序中的大部分同步代碼一般都處于無(wú)鎖競(jìng)爭(zhēng)狀態(tài)
(即單線程執(zhí)行環(huán)境),在無(wú)鎖競(jìng)爭(zhēng)的情況下完全可以避免調(diào)用操作系統(tǒng)層面的重量級(jí)互斥鎖泞边,
取而代之的是在monitorenter和monitorexit中只需要依靠一條CAS原子指令就可以完成鎖的獲取及釋放该押。
當(dāng)存在鎖競(jìng)爭(zhēng)的情況下,執(zhí)行CAS指令失敗的線程將調(diào)用操作系統(tǒng)互斥鎖進(jìn)入到阻塞狀態(tài)阵谚,當(dāng)鎖被釋放的時(shí)候被喚醒(具體處理步驟下面詳細(xì)討論)蚕礼。
偏向鎖(Biased Locking):是為了在無(wú)鎖競(jìng)爭(zhēng)的情況下避免在鎖獲取過(guò)程中執(zhí)行不必要的CAS原子指令,
因?yàn)镃AS原子指令雖然相對(duì)于重量級(jí)鎖來(lái)說(shuō)開(kāi)銷(xiāo)比較小但還是存在非成沂玻可觀的本地延遲(可參考這篇文章)奠蹬。
適應(yīng)性自旋(Adaptive Spinning):當(dāng)線程在獲取輕量級(jí)鎖的過(guò)程中執(zhí)行CAS操作失敗時(shí),在進(jìn)入與monitor相關(guān)聯(lián)的操作系統(tǒng)重量級(jí)鎖
(mutex semaphore)前會(huì)進(jìn)入忙等待(Spinning)然后再次嘗試嗡午,當(dāng)嘗試一定的次數(shù)后如果仍然沒(méi)有成功則調(diào)用與該monitor關(guān)聯(lián)的semaphore(即互斥鎖)囤躁,
進(jìn)入到阻塞狀態(tài)。
注:(適應(yīng)性)自旋鎖,是在從輕量級(jí)鎖向重量級(jí)鎖膨脹的過(guò)程中使用的割以,是在進(jìn)入重量級(jí)鎖之前進(jìn)行的金度。
鎖存在Java對(duì)象頭里应媚。如果對(duì)象是數(shù)組類型严沥,則虛擬機(jī)用3個(gè)Word(字寬)存儲(chǔ)對(duì)象頭,如果對(duì)象是非數(shù)組類型中姜,則用2字寬存儲(chǔ)對(duì)象頭消玄。
在32位虛擬機(jī)中,一字寬等于四字節(jié)丢胚,即32bit翩瓜。
鎖狀態(tài)包括:輕量級(jí)鎖定、重量級(jí)鎖定携龟、GC標(biāo)記兔跌、可偏向
簡(jiǎn)單的加鎖機(jī)制:
機(jī)制:每個(gè)鎖都關(guān)聯(lián)一個(gè)請(qǐng)求計(jì)數(shù)器和一個(gè)占有他的線程,當(dāng)請(qǐng)求計(jì)數(shù)器為0時(shí)峡蟋,這個(gè)鎖可以被認(rèn)為是unhled的坟桅,
當(dāng)一個(gè)線程請(qǐng)求一個(gè)unheld的鎖時(shí),JVM記錄鎖的擁有者蕊蝗,并把鎖的請(qǐng)求計(jì)數(shù)加1仅乓,如果同一個(gè)線程再次請(qǐng)求這個(gè)鎖時(shí),請(qǐng)求計(jì)數(shù)器就會(huì)增加蓬戚,
當(dāng)該線程退出syncronized塊時(shí)夸楣,計(jì)數(shù)器減1,當(dāng)計(jì)數(shù)器為0時(shí)子漩,鎖被釋放(這就保證了鎖是可重入的豫喧,不會(huì)發(fā)生死鎖的情況)。
偏向鎖流程:
偏向鎖幢泼,簡(jiǎn)單的講嘿棘,就是在鎖對(duì)象的對(duì)象頭中有個(gè)ThreaddId字段,這個(gè)字段如果是空的旭绒,
第一次獲取鎖的時(shí)候鸟妙,就將自身的ThreadId寫(xiě)入到鎖的ThreadId字段內(nèi),將鎖頭內(nèi)的是否偏向鎖的狀態(tài)位置1.
這樣下次獲取鎖的時(shí)候挥吵,直接檢查T(mén)hreadId是否和自身線程Id一致重父,如果一致,則認(rèn)為當(dāng)前線程已經(jīng)獲取了鎖忽匈,因此不需再次獲取鎖房午,
略過(guò)了輕量級(jí)鎖和重量級(jí)鎖的加鎖階段。提高了效率丹允。
但是偏向鎖也有一個(gè)問(wèn)題郭厌,就是當(dāng)鎖有競(jìng)爭(zhēng)關(guān)系的時(shí)候袋倔,需要解除偏向鎖,使鎖進(jìn)入競(jìng)爭(zhēng)的狀態(tài)折柠。
下面是清晰的流程:
上圖中只講了偏向鎖的釋放宾娜,其實(shí)還涉及偏向鎖的搶占,其實(shí)就是兩個(gè)進(jìn)程對(duì)鎖的搶占扇售,在synchrnized鎖下表現(xiàn)為輕量鎖方式進(jìn)行搶占前塔。
注:也就是說(shuō)一旦偏向鎖沖突,雙方都會(huì)升級(jí)為輕量級(jí)鎖承冰。(這一點(diǎn)與輕量級(jí)->重量級(jí)鎖不同华弓,那時(shí)候失敗一方直接升級(jí),成功一方在釋放時(shí)候notify困乒,加下文后面詳細(xì)描述)
如下圖寂屏。之后會(huì)進(jìn)入到輕量級(jí)鎖階段,兩個(gè)線程進(jìn)入鎖競(jìng)爭(zhēng)狀態(tài)(注娜搂,我理解仍然會(huì)遵守先來(lái)后到原則迁霎;注2,的確是的涌攻,下圖中提到了mark word中的lock record指向堆棧中最近的一個(gè)線程的lock record)欧引,一個(gè)具體例子可以參考synchronized鎖機(jī)制。(圖后面有介紹)
每一個(gè)線程在準(zhǔn)備獲取共享資源時(shí):
第一步恳谎,檢查MarkWord里面是不是放的自己的ThreadId ,如果是芝此,表示當(dāng)前線程是處于 “偏向鎖”
第二步,如果MarkWord不是自己的ThreadId,鎖升級(jí)因痛,這時(shí)候婚苹,用CAS來(lái)執(zhí)行切換,新的線程根據(jù)MarkWord里面現(xiàn)有的ThreadId鸵膏,通知之前線程暫停膊升,
之前線程將Markword的內(nèi)容置為空。
第三步谭企,兩個(gè)線程都把對(duì)象的HashCode復(fù)制到自己新建的用于存儲(chǔ)鎖的記錄空間廓译,接著開(kāi)始通過(guò)CAS操作,
把共享對(duì)象的MarKword的內(nèi)容修改為自己新建的記錄空間的地址的方式競(jìng)爭(zhēng)MarkWord,
第四步债查,第三步中成功執(zhí)行CAS的獲得資源非区,失敗的則進(jìn)入自旋
第五步,自旋的線程在自旋過(guò)程中盹廷,成功獲得資源(即之前獲的資源的線程執(zhí)行完成并釋放了共享資源)征绸,則整個(gè)狀態(tài)依然處于 輕量級(jí)鎖的狀態(tài),如果自旋失敗
第六步,進(jìn)入重量級(jí)鎖的狀態(tài)管怠,這個(gè)時(shí)候淆衷,自旋的線程進(jìn)行阻塞,等待之前線程執(zhí)行完成并喚醒自己
復(fù)制代碼
總結(jié):
偏向鎖渤弛,其實(shí)是無(wú)鎖競(jìng)爭(zhēng)下可重入鎖的簡(jiǎn)單實(shí)現(xiàn)祝拯。流程是這樣的 偏向鎖->輕量級(jí)鎖->重量級(jí)鎖
同步的原理
JVM規(guī)范規(guī)定JVM基于進(jìn)入和退出Monitor對(duì)象來(lái)實(shí)現(xiàn)方法同步和代碼塊同步,但兩者的實(shí)現(xiàn)細(xì)節(jié)不一樣暮芭。
代碼塊同步是使用monitorenter和monitorexit指令實(shí)現(xiàn)鹿驼,而方法同步是使用另外一種方式實(shí)現(xiàn)的欲低,細(xì)節(jié)在JVM規(guī)范里并沒(méi)有詳細(xì)說(shuō)明辕宏,但是方法的同步同樣可以使用這兩個(gè)指令來(lái)實(shí)現(xiàn)。
monitorenter指令是在編譯后插入到同步代碼塊的開(kāi)始位置砾莱,而monitorexit是插入到方法結(jié)束處和異常處瑞筐, JVM要保證每個(gè)monitorenter必須有對(duì)應(yīng)的monitorexit與之配對(duì)。
任何對(duì)象都有一個(gè) monitor 與之關(guān)聯(lián)腊瑟,當(dāng)且一個(gè)monitor 被持有后聚假,它將處于鎖定狀態(tài)。線程執(zhí)行到 monitorenter 指令時(shí)闰非,將會(huì)嘗試獲取對(duì)象所對(duì)應(yīng)的 monitor 的所有權(quán)膘格,即嘗試獲得對(duì)象的鎖。
Java對(duì)象頭
鎖存在Java對(duì)象頭里财松。如果對(duì)象是數(shù)組類型瘪贱,則虛擬機(jī)用3個(gè)Word(字寬)存儲(chǔ)對(duì)象頭,如果對(duì)象是非數(shù)組類型辆毡,則用2字寬存儲(chǔ)對(duì)象頭菜秦。在32位虛擬機(jī)中,一字寬等于四字節(jié)舶掖,即32bit球昨。(下面這個(gè)表格講的很清楚)
Java對(duì)象頭里的Mark Word里默認(rèn)存儲(chǔ)對(duì)象的HashCode,分代年齡和鎖標(biāo)記位眨攘。32位JVM的Mark Word的默認(rèn)存儲(chǔ)結(jié)構(gòu)如下:
在運(yùn)行期間Mark Word里存儲(chǔ)的數(shù)據(jù)會(huì)隨著鎖標(biāo)志位的變化而變化主慰。Mark Word可能變化為存儲(chǔ)以下4種數(shù)據(jù):
上圖里面的GC標(biāo)記,為11的話鲫售,推斷應(yīng)該是準(zhǔn)備GC的意思共螺。
在64位虛擬機(jī)下,Mark Word是64bit大小的龟虎,其存儲(chǔ)結(jié)構(gòu)如下:
鎖的升級(jí)
Java SE1.6為了減少獲得鎖和釋放鎖所帶來(lái)的性能消耗璃谨,引入了“偏向鎖”和“輕量級(jí)鎖”,
所以在Java SE1.6里鎖一共有四種狀態(tài),無(wú)鎖狀態(tài)佳吞,偏向鎖狀態(tài)拱雏,輕量級(jí)鎖狀態(tài)和重量級(jí)鎖狀態(tài),它會(huì)隨著競(jìng)爭(zhēng)情況逐漸升級(jí)底扳。
鎖可以升級(jí)但不能降級(jí)铸抑,意味著偏向鎖升級(jí)成輕量級(jí)鎖后不能降級(jí)成偏向鎖。
這種鎖升級(jí)卻不能降級(jí)的策略衷模,目的是為了提高獲得鎖和釋放鎖的效率鹊汛,下文會(huì)詳細(xì)分析。
偏向鎖
復(fù)制代碼
Hotspot的作者經(jīng)過(guò)以往的研究發(fā)現(xiàn)大多數(shù)情況下鎖不僅不存在多線程競(jìng)爭(zhēng)阱冶,而且總是由同一線程多次獲得刁憋,為了讓線程獲得鎖的代價(jià)更低而引入了偏向鎖。
當(dāng)一個(gè)線程訪問(wèn)同步塊并獲取鎖時(shí)木蹬,會(huì)在對(duì)象頭和棧幀中的鎖記錄里存儲(chǔ)鎖偏向的線程ID至耻,
以后該線程在進(jìn)入和退出同步塊時(shí)不需要花費(fèi)CAS操作來(lái)加鎖和解鎖,而只需簡(jiǎn)單的測(cè)試一下對(duì)象頭的Mark Word里是否存儲(chǔ)著指向當(dāng)前線程的偏向鎖镊叁,
如果測(cè)試成功尘颓,表示線程已經(jīng)獲得了鎖,如果測(cè)試失敗晦譬,則需要再測(cè)試下Mark Word中偏向鎖的標(biāo)識(shí)是否設(shè)置成1(表示當(dāng)前是偏向鎖)疤苹,如果沒(méi)有設(shè)置,
則使用CAS競(jìng)爭(zhēng)鎖敛腌,如果設(shè)置了卧土,則嘗試使用CAS將對(duì)象頭的偏向鎖指向當(dāng)前線程。
偏向鎖的撤銷(xiāo):偏向鎖使用了一種等到競(jìng)爭(zhēng)出現(xiàn)才釋放鎖的機(jī)制迎瞧,所以當(dāng)其他線程嘗試競(jìng)爭(zhēng)偏向鎖時(shí)夸溶,持有偏向鎖的線程才會(huì)釋放鎖。
偏向鎖的撤銷(xiāo)凶硅,需要等待全局安全點(diǎn)(在這個(gè)時(shí)間點(diǎn)上沒(méi)有字節(jié)碼正在執(zhí)行)缝裁,
它會(huì)首先暫停擁有偏向鎖的線程,然后檢查持有偏向鎖的線程是否活著足绅,
如果線程不處于活動(dòng)狀態(tài)捷绑,則將對(duì)象頭設(shè)置成無(wú)鎖狀態(tài),
如果線程仍然活著氢妈,擁有偏向鎖的棧會(huì)被執(zhí)行粹污,遍歷偏向?qū)ο蟮逆i記錄,
棧中的鎖記錄和對(duì)象頭的Mark Word要么重新偏向于其他線程首量,要么恢復(fù)到無(wú)鎖或者標(biāo)記對(duì)象不適合作為偏向鎖壮吩,最后喚醒暫停的線程进苍。
上面的意思是,先暫停持有偏向鎖的線程鸭叙,嘗試直接切換觉啊。如果不成功,就繼續(xù)運(yùn)行沈贝,并且標(biāo)記對(duì)象不適合偏向鎖杠人,鎖膨脹(鎖升級(jí))。
詳見(jiàn)宋下,上面有張圖中的“偏向鎖搶占模式”:
其中提到了mark word中的lock record指向堆棧最近的一個(gè)線程的lock record嗡善,其實(shí)就是按照先來(lái)后到模式進(jìn)行了輕量級(jí)的加鎖。
復(fù)制代碼
上文提到全局安全點(diǎn):在這個(gè)時(shí)間點(diǎn)上沒(méi)有字節(jié)碼正在執(zhí)行学歧。
關(guān)閉偏向鎖:偏向鎖在Java 6和Java 7里是默認(rèn)啟用的罩引,但是它在應(yīng)用程序啟動(dòng)幾秒鐘之后才激活,
如有必要可以使用JVM參數(shù)來(lái)關(guān)閉延遲-XX:BiasedLockingStartupDelay = 0撩满。
如果你確定自己應(yīng)用程序里所有的鎖通常情況下處于競(jìng)爭(zhēng)狀態(tài)蜒程,可以通過(guò)JVM參數(shù)關(guān)閉偏向鎖-XX:-UseBiasedLocking=false绅你,那么默認(rèn)會(huì)進(jìn)入輕量級(jí)鎖狀態(tài)伺帘。
輕量級(jí)鎖
輕量級(jí)鎖加鎖:線程在執(zhí)行同步塊之前,JVM會(huì)先在當(dāng)前線程的棧楨中創(chuàng)建用于存儲(chǔ)鎖記錄的空間忌锯,并將對(duì)象頭中的Mark Word復(fù)制到鎖記錄中伪嫁,官方稱為Displaced Mark Word。
然后線程嘗試使用CAS將對(duì)象頭中的Mark Word替換為指向鎖記錄的指針偶垮。如果成功张咳,當(dāng)前線程獲得鎖,如果失敗似舵,表示其他線程競(jìng)爭(zhēng)鎖脚猾,當(dāng)前線程便嘗試使用自旋來(lái)獲取鎖。
輕量級(jí)鎖解鎖:輕量級(jí)解鎖時(shí)砚哗,會(huì)使用原子的CAS操作來(lái)將Displaced Mark Word替換回到對(duì)象頭龙助,如果成功,則表示沒(méi)有競(jìng)爭(zhēng)發(fā)生蛛芥。
如果失敗提鸟,表示當(dāng)前鎖存在競(jìng)爭(zhēng),鎖就會(huì)膨脹成重量級(jí)鎖仅淑。
注:輕量級(jí)鎖會(huì)一直保持称勋,喚醒總是發(fā)生在輕量級(jí)鎖解鎖的時(shí)候,因?yàn)榧渔i的時(shí)候已經(jīng)成功CAS操作涯竟;而CAS失敗的線程赡鲜,會(huì)立即鎖膨脹空厌,并阻塞等待喚醒。(詳見(jiàn)下圖)
下圖是兩個(gè)線程同時(shí)爭(zhēng)奪鎖银酬,導(dǎo)致鎖膨脹的流程圖蝇庭。
鎖不會(huì)降級(jí)
因?yàn)樽孕龝?huì)消耗CPU,為了避免無(wú)用的自旋(比如獲得鎖的線程被阻塞住了)捡硅,一旦鎖升級(jí)成重量級(jí)鎖哮内,就不會(huì)再恢復(fù)到輕量級(jí)鎖狀態(tài)。
當(dāng)鎖處于這個(gè)狀態(tài)下壮韭,其他線程試圖獲取鎖時(shí)北发,都會(huì)被阻塞住,當(dāng)持有鎖的線程釋放鎖之后會(huì)喚醒這些線程喷屋,被喚醒的線程就會(huì)進(jìn)行新一輪的奪鎖之爭(zhēng)琳拨。
輕量級(jí)鎖具體實(shí)現(xiàn):
一個(gè)線程能夠通過(guò)兩種方式鎖住一個(gè)對(duì)象:1、通過(guò)膨脹一個(gè)處于無(wú)鎖狀態(tài)(狀態(tài)位001)的對(duì)象獲得該對(duì)象的鎖屯曹;
2狱庇、對(duì)象已經(jīng)處于膨脹狀態(tài)(狀態(tài)位00)但LockWord指向的monitor record的Owner字段為NULL,
則可以直接通過(guò)CAS原子指令嘗試將Owner設(shè)置為自己的標(biāo)識(shí)來(lái)獲得鎖恶耽。
從中可以看出密任,是先檢查鎖的標(biāo)識(shí)位。
CAS應(yīng)用
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,否則什么都不做负溪。
復(fù)制代碼
下面從分析比較常用的CPU(intel x86)來(lái)解釋CAS的實(shí)現(xiàn)原理透揣。
下面是sun.misc.Unsafe類的compareAndSwapInt()方法的源代碼:
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
可以看到這是個(gè)本地方法調(diào)用。這個(gè)本地方法在openjdk中依次調(diào)用的c++代碼為:unsafe.cpp川抡,atomic.cpp和atomicwindowsx86.inline.hpp辐真。
復(fù)制代碼
對(duì)于32位/64位的操作應(yīng)該是原子的:
奔騰6和最新的處理器能自動(dòng)保證單處理器對(duì)同一個(gè)緩存行里進(jìn)行16/32/64位的操作是原子的,但是復(fù)雜的內(nèi)存操作處理器不能自動(dòng)保證其原子性猖腕,
比如跨總線寬度拆祈,跨多個(gè)緩存行,跨頁(yè)表的訪問(wèn)倘感。但是處理器提供總線鎖定和緩存鎖定兩個(gè)機(jī)制來(lái)保證復(fù)雜內(nèi)存操作的原子性放坏。
CAS的缺點(diǎn)
復(fù)制代碼
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)加一,那么A-B-A 就會(huì)變成1A-2B-3A梗摇。
從Java1.5開(kāi)始JDK的atomic包里提供了一個(gè)類AtomicStampedReference來(lái)解決ABA問(wèn)題拓哟。
這個(gè)類的compareAndSet方法作用是首先檢查當(dāng)前引用是否等于預(yù)期引用,并且當(dāng)前標(biāo)志是否等于預(yù)期標(biāo)志伶授,如果全部相等断序,
則以原子方式將該引用和該標(biāo)志的值設(shè)置為給定的更新值。
關(guān)于ABA問(wèn)題參考文檔: http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html
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í)行指令(de-pipeline),使CPU不會(huì)消耗過(guò)多的執(zhí)行資源,
延遲的時(shí)間取決于具體實(shí)現(xiàn)的版本挚币,在一些處理器上延遲時(shí)間是零。
第二它可以避免在退出循環(huán)的時(shí)候因內(nèi)存順序沖突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush)扣典,從而提高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痢畜。
從Java1.5開(kāi)始JDK提供了AtomicReference類來(lái)保證引用對(duì)象之間的原子性垛膝,你可以把多個(gè)變量放在一個(gè)對(duì)象里來(lái)進(jìn)行CAS操作鳍侣。
轉(zhuǎn)載自http://www.cnblogs.com/charlesblc/p/5994162.html這邊博客寫(xiě)的太好了!只是調(diào)整了排版然后刪除了一些冗余段落直接復(fù)用了吼拥!這都是滿滿的干貨耙芯邸!