Java并發(fā)那些事兒-優(yōu)化鎖

1.0

樂觀鎖和悲觀鎖

對(duì)于同一個(gè)數(shù)據(jù)的并發(fā)操作窃判,悲觀鎖認(rèn)為在使用數(shù)據(jù)的時(shí)候一定會(huì)有別的線程來修改數(shù)據(jù),因此在獲取數(shù)據(jù)的時(shí)候會(huì)先加鎖匈辱,確保數(shù)據(jù)不會(huì)被別的線程修改寺擂。synchronized關(guān)鍵字和Lock的實(shí)現(xiàn)類就是悲觀鎖;樂觀鎖認(rèn)為在使用數(shù)據(jù)時(shí)不會(huì)有別的線程修改數(shù)據(jù)砾医,所有不會(huì)添加鎖拿撩,只是在更新數(shù)據(jù)的時(shí)候去判斷之前有沒有別的線程更新了這個(gè)數(shù)據(jù)。如果這個(gè)數(shù)據(jù)沒有被更新如蚜,當(dāng)前線程將自己修改的數(shù)據(jù)成功寫入压恒;如果數(shù)據(jù)已經(jīng)被其他線程更新,則根據(jù)不同的實(shí)現(xiàn)方式執(zhí)行不同的操作(報(bào)錯(cuò)或者自動(dòng)重試)错邦。

悲觀鎖適合寫操作多的場(chǎng)景探赫,先加鎖可以保證寫操作時(shí)數(shù)據(jù)正確;樂觀鎖適合讀操作多的場(chǎng)景撬呢,不加鎖的特點(diǎn)能夠使其讀操作的性能大幅提升伦吠。

悲觀鎖都是在鎖定之后再操作同步資源;樂觀鎖是直接去操作同步資源魂拦,樂觀鎖采用的是CAS來實(shí)現(xiàn)在不鎖定同步資源的情況下實(shí)現(xiàn)線程同步毛仪。CAS:參考:Java并發(fā)那些事兒-CAS - 簡(jiǎn)書

無鎖

無鎖沒有對(duì)資源進(jìn)行鎖定,所有的線程都能訪問并修改同一個(gè)資源芯勘,但同時(shí)只有一個(gè)線程能修改成功箱靴。

無鎖的特點(diǎn)就是修改操作在循環(huán)內(nèi)進(jìn)行,線程會(huì)不斷的嘗試修改共享資源荷愕。如果沒有沖突就修改成功并退出衡怀,否則就會(huì)繼續(xù)循環(huán)嘗試。如果有多個(gè)線程修改同一個(gè)值安疗,必定會(huì)有一個(gè)線程能修改成功抛杨,而其他修改失敗的線程會(huì)不斷重試直到修改成功(CAS原理及應(yīng)用即是無鎖的實(shí)現(xiàn))。無鎖無法全面代替有鎖茂契,但無鎖在某些場(chǎng)合下的性能是非常高的蝶桶。

偏向鎖

偏向鎖是指一段同步代碼一直被一個(gè)線程所訪問,那么該線程會(huì)自動(dòng)獲取鎖掉冶,降低獲取鎖的代價(jià)真竖。在大多數(shù)情況下,鎖總是由同一線程多次獲得厌小,不存在多線程競(jìng)爭(zhēng)恢共,所以出現(xiàn)了偏向鎖。其目標(biāo)就是在只有一個(gè)線程執(zhí)行同步代碼塊時(shí)能夠提高性能璧亚。

原理:當(dāng)一個(gè)線程訪問同步代碼塊并獲取鎖時(shí)讨韭,會(huì)在Mark Word里存儲(chǔ)鎖偏向的線程ID。在線程進(jìn)入和退出同步塊時(shí)不再通過CAS操作來加鎖和解鎖,而是檢測(cè)Mark Word里是否存儲(chǔ)著指向當(dāng)前線程的偏向鎖透硝。引入偏向鎖是為了在無多線程競(jìng)爭(zhēng)的情況下盡量減少不必要的輕量級(jí)鎖執(zhí)行路徑狰闪,因?yàn)檩p量級(jí)鎖的獲取及釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時(shí)候依賴一次CAS原子指令即可濒生。

偏向鎖只有遇到其他線程嘗試競(jìng)爭(zhēng)偏向鎖時(shí)埋泵,持有偏向鎖的線程才會(huì)釋放鎖,線程不會(huì)主動(dòng)釋放偏向鎖罪治。偏向鎖的撤銷丽声,需要等待全局安全點(diǎn)(在這個(gè)時(shí)間點(diǎn)上沒有字節(jié)碼正在執(zhí)行),它會(huì)首先暫停擁有偏向鎖的線程觉义,判斷鎖對(duì)象是否處于被鎖定狀態(tài)雁社。撤銷偏向鎖后恢復(fù)到無鎖(標(biāo)志位為“01”)或輕量級(jí)鎖(標(biāo)志位為“00”)的狀態(tài)。

輕量級(jí)鎖

當(dāng)鎖是偏向鎖的時(shí)候晒骇,被另外的線程所訪問霉撵,偏向鎖就會(huì)升級(jí)為輕量級(jí)鎖,其他線程會(huì)通過自旋的形式嘗試獲取鎖厉碟,不會(huì)阻塞喊巍,從而提高性能。

輕量級(jí)鎖不是用來代替重量級(jí)鎖的箍鼓,它的作用是在沒有多線程競(jìng)爭(zhēng)的前提下,減少重量級(jí)鎖使用產(chǎn)生的性能消耗呵曹。輕量級(jí)鎖所適應(yīng)的場(chǎng)景是線程交替執(zhí)行同步塊的情況款咖,如果同一個(gè)線程訪問同一個(gè)鎖的情況,這個(gè)時(shí)候會(huì)膨脹成重量級(jí)鎖(這種情況下JIT會(huì)使用鎖消除加以優(yōu)化)奄喂。

加鎖過程

1铐殃,在代碼進(jìn)入同步塊的時(shí)候,如果同步對(duì)象鎖狀態(tài)為無鎖狀態(tài)(沒有被任何線程鎖定跨新。鎖標(biāo)志位為“01”狀態(tài)富腊,是否為偏向鎖為“0”),虛擬機(jī)首先將在當(dāng)前線程的棧幀中建立一個(gè)名為鎖記錄(Lock Record)的空間(同步塊是一個(gè)方法或者方法中的代碼塊域帐。線程在執(zhí)行方法的時(shí)候赘被,會(huì)在虛擬機(jī)棧中創(chuàng)建一個(gè)該方法的棧幀),用于存儲(chǔ)鎖對(duì)象目前的Mark Word(保存的鎖信息)的拷貝肖揣。

2民假,拷貝鎖對(duì)象的對(duì)象頭中的Mark Word復(fù)制到棧幀的鎖記錄中。

3龙优,拷貝成功后羊异,虛擬機(jī)將使用CAS操作嘗試將鎖對(duì)象的Mark Word更新為指向Lock Record的指針,并將Lock record里的owner指針指向鎖對(duì)象的Mark word。

4野舶,如果步驟三成功:那么當(dāng)前線程就擁有了該對(duì)象的鎖易迹,并且鎖對(duì)象Mark Word的鎖標(biāo)志位設(shè)置為“00”,即表示此鎖對(duì)象處于輕量級(jí)鎖定狀態(tài)平道。

5睹欲,如果更新操作失敗了,虛擬機(jī)首先會(huì)檢查鎖對(duì)象的Mark word是否指向當(dāng)前線程的棧幀巢掺,如果是句伶,就說明當(dāng)前線程已經(jīng)擁有了鎖對(duì)象的鎖,那就直接進(jìn)入同步塊進(jìn)行執(zhí)行陆淀。否則說明多個(gè)線程競(jìng)爭(zhēng)鎖考余。

若當(dāng)前只有一個(gè)等待線程,則該線程通過自旋進(jìn)行等待轧苫。但是當(dāng)自旋超過一定的次數(shù)楚堤,或者一個(gè)線程在持有鎖,一個(gè)在自旋含懊,又有第三個(gè)來訪時(shí)身冬, 輕量級(jí)鎖升級(jí)為重量級(jí)鎖。

釋放鎖

1岔乔,通過CAS操作嘗試把線程中復(fù)制的鎖對(duì)象的對(duì)象頭的Mark Word替換鎖對(duì)象的Mark Word信息(就是把加鎖的時(shí)候復(fù)制的鎖對(duì)象的對(duì)象頭的Mark Word再重新還給鎖對(duì)象)酥筝。

2,如果替換成功雏门,整個(gè)同步過程就完成了嘿歌。

3,如果替換失敗茁影,說明有其他線程嘗試過獲取該鎖(此時(shí)鎖已經(jīng)膨脹)宙帝,那就在釋放鎖的同時(shí),喚醒被掛起的線程募闲。

重量級(jí)鎖

輕量級(jí)鎖升級(jí)為重量級(jí)鎖步脓。鎖標(biāo)志變?yōu)椤?0”。鎖對(duì)象的Mark Word中存儲(chǔ)的就是指向重量級(jí)鎖的指針浩螺,后面等待鎖的線程也要進(jìn)入阻塞狀態(tài)靴患。

自旋鎖

在Java程序中,其實(shí)共享數(shù)據(jù)的鎖定狀態(tài)一般持續(xù)時(shí)間很短年扩,如果在這段時(shí)間讓線程阻塞或掛起(線程狀態(tài)的轉(zhuǎn)換)再恢復(fù)線程蚁廓,這樣做很浪費(fèi)時(shí)間。

現(xiàn)在的物理機(jī)上都會(huì)有多個(gè)處理器厨幻,可以讓多個(gè)線程同時(shí)執(zhí)行相嵌,這時(shí)就可以讓后來的線程“等一下”腿时,但是并不是放棄CPU的執(zhí)行時(shí)間,看看持有鎖的線程會(huì)不會(huì)很快的釋放鎖饭宾。這個(gè)“等一下”的過程就是自旋批糟。

自旋鎖和阻塞鎖最大的區(qū)別就是,到底要不要放棄處理器的執(zhí)行時(shí)間看铆。對(duì)于阻塞鎖和自旋鎖來說徽鼎,都是要等待獲得共享資源。但是阻塞鎖是放棄了CPU時(shí)間弹惦,進(jìn)入了等待區(qū)否淤,等待被喚醒。而自旋鎖是一直“自旋”在那里棠隐,時(shí)刻的檢查共享資源是否可以被訪問石抡。

由于自旋鎖只是將當(dāng)前線程不停地執(zhí)行循環(huán)體,不進(jìn)行線程狀態(tài)的改變助泽,所以響應(yīng)速度更快啰扛。但當(dāng)線程數(shù)不停增加時(shí),性能下降明顯嗡贺,因?yàn)槊總€(gè)線程都需要執(zhí)行隐解,占用CPU時(shí)間。如果線程競(jìng)爭(zhēng)不激烈诫睬,并且保持鎖的時(shí)間短煞茫。適合使用自旋鎖。

在AtomicInteger的CAS算法中摄凡,就采用了自旋來等待鎖的釋放溜嗜。

Unsafe.class

do-while循環(huán)就是一個(gè)自旋操作,如果修改數(shù)值失敗則通過循環(huán)來執(zhí)行自旋架谎,直至修改成功。

自適應(yīng)自旋鎖

自適應(yīng)意味著自旋的時(shí)間不再固定辟躏,而是由前一次在同一個(gè)鎖上的自旋時(shí)間及鎖的擁有者的狀態(tài)來決定谷扣。如果在同一個(gè)鎖對(duì)象上,自旋等待剛剛成功獲得過鎖捎琐,并且持有鎖的線程正在運(yùn)行中会涎,那么虛擬機(jī)就會(huì)認(rèn)為這次自旋也是很有可能再次成功,進(jìn)而它將允許自旋等待持續(xù)相對(duì)更長(zhǎng)的時(shí)間瑞凑。如果對(duì)于某個(gè)鎖末秃,自旋很少成功獲得過,那在以后嘗試獲取這個(gè)鎖時(shí)將可能省略掉自旋過程籽御,直接阻塞線程练慕,避免浪費(fèi)處理器資源惰匙。

鎖消除

“鎖消除”,是JIT編譯器對(duì)內(nèi)部鎖的具體實(shí)現(xiàn)所做的一種優(yōu)化铃将。

在動(dòng)態(tài)編譯同步塊的時(shí)候项鬼,JIT編譯器借助逃逸分析(Escape Analysis)的技術(shù)來判斷同步塊所使用的鎖對(duì)象是否只能夠被一個(gè)線程訪問。

如果同步塊所使用的鎖對(duì)象通過這種分析被證實(shí)只能夠被一個(gè)線程訪問劲阎,那么JIT編譯器在編譯這個(gè)同步塊的時(shí)候就會(huì)取消對(duì)這部分代碼的同步绘盟。

鎖粗化

在代碼中,需要加鎖的時(shí)候悯仙,提倡盡量減小鎖的粒度龄毡,這樣可以避免不必要的阻塞。加鎖的時(shí)候锡垄,把無關(guān)的準(zhǔn)備工作放到鎖外面沦零,鎖內(nèi)部只處理和并發(fā)相關(guān)的內(nèi)容。這樣有助于提高效率偎捎。

如果在一段代碼中連續(xù)的對(duì)同一個(gè)對(duì)象反復(fù)加鎖解鎖蠢终,其實(shí)是相對(duì)耗費(fèi)資源的,這種情況可以適當(dāng)放寬加鎖的范圍茴她,減少性能消耗寻拂。當(dāng)JIT發(fā)現(xiàn)一系列連續(xù)的操作都對(duì)同一個(gè)對(duì)象反復(fù)加鎖和解鎖,甚至加鎖操作出現(xiàn)在循環(huán)體中的時(shí)候丈牢,會(huì)將加鎖同步的范圍擴(kuò)散(粗化)到整個(gè)操作序列的外部祭钉。

減小鎖粒度強(qiáng)調(diào)的是不要在鎖內(nèi)部處理和鎖無關(guān)的事情;鎖粗化建議的是一個(gè)功能己沛,要處理多個(gè)業(yè)務(wù)的時(shí)候慌核,在一個(gè)線程里面執(zhí)行而不是分為多個(gè)線程執(zhí)行,減少線程之間切換和鎖等待時(shí)間申尼。

偏向鎖通過對(duì)比Mark Word解決加鎖問題垮卓,避免執(zhí)行CAS操作。而輕量級(jí)鎖是通過用CAS操作和自旋來解決加鎖問題师幕,避免線程阻塞和喚醒而影響性能粟按。重量級(jí)鎖是將除了擁有鎖的線程以外的線程都阻塞。

參考:

Java并發(fā)編程:Synchronized底層優(yōu)化(偏向鎖霹粥、輕量級(jí)鎖) - liuxiaopeng - 博客園

不可不說的Java“鎖”事 - 美團(tuán)技術(shù)團(tuán)隊(duì)

?著作權(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)離奇詭異捌朴,居然都是意外死亡吴攒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門男旗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來舶斧,“玉大人,你說我怎么就攤上這事察皇≤罾鳎” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵什荣,是天一觀的道長(zhǎng)矾缓。 經(jīng)常有香客問我,道長(zhǎng)稻爬,這世上最難降的妖魔是什么嗜闻? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮桅锄,結(jié)果婚禮上琉雳,老公的妹妹穿的比我還像新娘。我一直安慰自己友瘤,他們只是感情好翠肘,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著辫秧,像睡著了一般束倍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上盟戏,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天绪妹,我揣著相機(jī)與錄音,去河邊找鬼柿究。 笑死邮旷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蝇摸。 我是一名探鬼主播廊移,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼探入!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起懂诗,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤蜂嗽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后殃恒,有當(dāng)?shù)厝嗽跇淞掷锇l(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
  • 文/蒙蒙 一吗冤、第九天 我趴在偏房一處隱蔽的房頂上張望又厉。 院中可真熱鬧,春花似錦椎瘟、人聲如沸覆致。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽篷朵。三九已至,卻和暖如春婆排,著一層夾襖步出監(jiān)牢的瞬間声旺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工段只, 沒想到剛下飛機(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)容