樂觀鎖和悲觀鎖
對(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算法中摄凡,就采用了自旋來等待鎖的釋放溜嗜。
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 - 博客園