鎖優(yōu)化
Jvm 在加鎖的過程中,會采用自旋、自適應(yīng)、鎖消除、鎖粗化等優(yōu)化手段來提升代碼執(zhí)行效率。
-
什么是鎖升級,降級?
鎖的4中狀態(tài):無鎖狀態(tài)、偏向鎖狀態(tài)、輕量級鎖狀態(tài)、重量級鎖狀態(tài)(級別從低到高)。
所謂的鎖升級、降級,就是 JVM 優(yōu)化 synchronized 運行的機制,當(dāng) JVM 監(jiān)測到不同的競爭狀況是,會自動切換到不同的鎖實現(xiàn)。這種切換就是鎖的升級、降級。

偏向鎖
因為經(jīng)過大量的研究發(fā)現(xiàn),大多數(shù)時候是不存在鎖競爭的,常常是一個線程多次獲得同一個鎖,因此如果每次都要競爭鎖會增大很多沒有必要付出的代價,為了降低獲取鎖的代價,才引入的偏向鎖。
在程序的一開始,處于無鎖狀態(tài)。緊接著,有一個線程申請鎖,此時通過CAS競爭鎖(CAS保證了此競爭行為的原子性),獲取鎖成功,Mark Word 將標(biāo)記為偏向鎖。當(dāng)同樣的線程再次到來,發(fā)現(xiàn)是鎖的持有者并且是偏向鎖,直接進入臨界區(qū)。
因此,偏向鎖意味著,不會發(fā)生競爭條件,因為只有一個線程。
輕量級鎖
隨著程序的運行,有新的線程要進入臨界區(qū),通過CAS競爭鎖失敗。Mark Word立即將偏向鎖標(biāo)記鎖為輕量級鎖,因為已經(jīng)發(fā)生了競爭條件。緊接著,會反復(fù)同通過CAS為線程獲取鎖,如果占有鎖的線程在臨界區(qū)待的時間很短,那么申請鎖的線程將很快拿到鎖。
因此,輕量級鎖意味著,有競爭條件,但是大家能很快地被分配到鎖。
重量級鎖
當(dāng)然,申請鎖的線程并不總是能很快地獲取到鎖,與其反復(fù)地CAS重試而浪費CPU時間,不如直接將線程阻塞住。那么,在輕量級鎖的情況下,如果有線程超過一定次數(shù)的重試還是獲取不到鎖,Mark Word立即將輕量級鎖標(biāo)記為重量級鎖,此后所有獲取不到鎖的線程將被阻塞,需要Monitor的參與。
因此,重量級鎖意味著,在有競爭條件的情況下,線程不能很快地被分配到鎖。
Synchronized的鎖只能膨脹,不能收縮。偏向鎖和輕量鎖為樂觀鎖,重量級鎖為悲觀鎖。
Synchronized的好處在于,它的優(yōu)化、鎖申請釋放、鎖的分配都是自動的,開發(fā)者能快速地使用。
輕量級鎖什么時候升級為重量級鎖?
線程1獲取輕量級鎖時會先把鎖對象的對象頭MarkWord復(fù)制一份到線程1的棧幀中創(chuàng)建的用于存儲鎖記錄的空間(稱為DisplacedMarkWord),然后使用CAS把對象頭中的內(nèi)容替換為線程1存儲的鎖記錄(DisplacedMarkWord)的地址;
如果在線程1復(fù)制對象頭的同時(在線程1CAS之前),線程2也準(zhǔn)備獲取鎖,復(fù)制了對象頭到線程2的鎖記錄空間中,但是在線程2CAS的時候,發(fā)現(xiàn)線程1已經(jīng)把對象頭換了,線程2的CAS失敗,那么線程2就嘗試使用自旋鎖來等待線程1釋放鎖。
但是如果自旋的時間太長也不行,因為自旋是要消耗CPU的,因此自旋的次數(shù)是有限制的,比如10次或者100次,如果自旋次數(shù)到了線程1還沒有釋放鎖,或者線程1還在執(zhí)行,線程2還在自旋等待,這時又有一個線程3過來競爭這個鎖對象,那么這個時候輕量級鎖就會膨脹為重量級鎖。重量級鎖把除了擁有鎖的線程都阻塞,防止CPU空轉(zhuǎn)。

鎖膨脹
當(dāng)輕量級鎖失敗,虛擬機就會使用重量級鎖。使用重量級鎖時,對象的Mark Word中的末尾的2位會被設(shè)置為'10'。整個Mark Word表示指向monitor對象的指針。
JVM對鎖的優(yōu)化
1.自旋鎖
在沒有加入鎖優(yōu)化時,Synchronized是一個非常“胖大”的家伙。在多線程競爭鎖時,當(dāng)一個線程獲取鎖時,它會阻塞所有正在競爭的線程,這樣對性能帶來了極大的影響。在掛起線程和恢復(fù)線程的操作都需要轉(zhuǎn)入內(nèi)核態(tài)中完成,這些操作對系統(tǒng)的并發(fā)性能帶來了很大的壓力。同時HotSpot團隊注意到在很多情況下,共享數(shù)據(jù)的鎖定狀態(tài)只會持續(xù)很短的一段時間,為了這段時間去掛起和回復(fù)阻塞線程并不值得。在如今多處理器環(huán)境下,完全可以讓另一個沒有獲取到鎖的線程在門外等待一會(自旋),但不放棄CPU的執(zhí)行時間。等待持有鎖的線程是否很快就會釋放鎖。為了讓線程等待,我們只需要讓線程執(zhí)行一個忙循環(huán)(自旋),這便是自旋鎖由來的原因。
2.鎖粗化
如果一系列的連續(xù)操作都是對同一個對象反復(fù)加鎖和解鎖,甚至加鎖操作是出現(xiàn)在循環(huán)體中的,那即使沒有線程競爭,頻繁地進行互斥同步操作也會導(dǎo)致不必要的性能損耗。在這種情況下,虛擬機便會把所有的鎖操作優(yōu)化成對鎖的一次請求,從而減少對鎖的請求同步次數(shù),這個操作叫做鎖粗化。
3.鎖消除
鎖消除是指JIT編譯器在運行時,將一些在代碼上同步了但實際上不可能存在共享數(shù)據(jù)競爭的鎖進行消除。
鎖消除主要是根據(jù)逃逸分析技術(shù)來判定的,如果判斷在一段代碼中,堆上的所有數(shù)據(jù)都不會逃逸出去從而被其它線程訪問到,那就可以把它們當(dāng)做棧上數(shù)據(jù)對待,認為它們是線程私有的,同步加鎖自然就無需進行了。