Java在語言上支持了鎖的特性袒餐,在很多常用類的實現(xiàn)中也使用了鎖础废,對于Java開發(fā)者來說就可以很方便的使用這些鎖及常用類篡诽。但是,隨著鎖的頻繁使用及錯用席镀,隨之而來的就是程序執(zhí)行效率變低匹中、應(yīng)用變的緩慢。為了提高線程對共享數(shù)據(jù)訪問的效率豪诲,HotSpot虛擬機(jī)從JDK1.5到JDK1.6做了重大改進(jìn)顶捷,提供了很多鎖優(yōu)化技術(shù),包括自旋鎖屎篱、自適應(yīng)自旋鎖服赎、鎖消除、鎖粗化交播、輕量級鎖和偏向鎖重虑。
自旋鎖
線程的執(zhí)行是通過競爭獲取處理器的執(zhí)行時間才執(zhí)行的。當(dāng)線程掛起或恢復(fù)執(zhí)行的時秦士,會從用戶態(tài)轉(zhuǎn)入內(nèi)核態(tài)中完成缺厉,這種操作是很消耗時間的,在并發(fā)情況下對應(yīng)用和系統(tǒng)來說都有很大壓力。HotSpot虛擬機(jī)的開發(fā)人員注意到很多應(yīng)用對共享數(shù)據(jù)鎖定的時間都是很短暫的提针,為了這短暫的時間而掛起和恢復(fù)線程是不值得的命爬。所以,線程并發(fā)請求鎖的時候辐脖,讓后來的線程在不放棄處理器執(zhí)行時間的情況下稍等一下遇骑,線程做自旋,自旋期間觀察持有鎖的線程是否會很快釋放鎖揖曾,這種技術(shù)就是所謂的自旋鎖。
自旋鎖在JDK1.6中是默認(rèn)開啟的亥啦,默認(rèn)自旋次數(shù)是10次炭剪,可以使用參數(shù)-XX:PreBlockSpin修改默認(rèn)值。雖然翔脱,自旋鎖避免了線程掛起和恢復(fù)的開銷奴拦,但是它占用了處理器的執(zhí)行時間,如果鎖占用時間很短届吁,自旋鎖效果很好错妖,否則會浪費處理器的執(zhí)行時間,影響應(yīng)用的整體性能疚沐。
自適應(yīng)自旋鎖
在JDK1.6中引入了自適應(yīng)自旋鎖暂氯,自旋的次數(shù)由上一次在同一個鎖上自旋的時間和鎖持有者的狀態(tài)來決定。如果上一次同一個鎖通過自旋剛剛被獲取亮蛔,并且持有鎖的線程正在運行痴施,那么虛擬機(jī)認(rèn)為本次自旋也會成功,將會自旋相對長的時間獲取鎖究流。如果同一個鎖很少通過自旋成功被獲取辣吃,那么虛擬機(jī)認(rèn)為本次自旋也會失敗,不會執(zhí)行自旋操作芬探。
鎖消除
一些使用了鎖控制的代碼神得,在虛擬機(jī)即時編譯器運行時檢測到不存在對共享數(shù)據(jù)的競爭訪問,也就是代碼只會被一個線程訪問偷仿,此時會對鎖進(jìn)行消除哩簿,這項優(yōu)化稱為鎖消除。鎖消除的主要判斷依據(jù)來源于逃逸分析(即分析對象的動態(tài)作用域炎疆,一個對象在方法內(nèi)被定義后卡骂,在別的方法或線程中無法通過任何途徑訪問到這個對象,則可以進(jìn)行一些優(yōu)化操作)的數(shù)據(jù)支持形入。
鎖粗化
大多數(shù)情況下全跨,為了提高程序的執(zhí)行效率,會縮小鎖作用的范圍亿遂。但是浓若,對于一些連續(xù)操作都對同一個對象進(jìn)行反復(fù)加鎖渺杉、釋放鎖的情況來說,縮小鎖的作用范圍會消耗更多的資源挪钓,這種情況需要擴(kuò)大鎖的作用范圍是越,這項優(yōu)化稱為鎖粗化。
輕量級鎖
了解輕量級鎖之前碌上,先了解一下Java對象在內(nèi)存中存儲的數(shù)據(jù)結(jié)構(gòu)倚评。在HotSpot虛擬機(jī)中,Java對象在內(nèi)存中存儲的布局分為3塊區(qū)域:對象頭馏予、實例數(shù)據(jù)和對齊填充天梧。對象頭包含兩部分,第一部分包含對象的HashCode霞丧、分代年齡呢岗、鎖標(biāo)志位、線程持有的鎖蛹尝、偏向線程ID等數(shù)據(jù)后豫,這部分?jǐn)?shù)據(jù)的長度在32位和64位虛擬機(jī)中分別為32bit和64bit,官方稱為Mark World突那〈炷穑考慮到虛擬機(jī)的空間效率,Mark World內(nèi)部的數(shù)據(jù)結(jié)構(gòu)是非固定的陨收,也就是說對象頭中存儲的內(nèi)容是不固定的饭豹,下圖展示了不同狀態(tài)下,對象頭中存儲的內(nèi)容:
當(dāng)代碼執(zhí)行到同步代碼時务漩,如果此時對象的鎖未被鎖定(鎖標(biāo)志位位01)拄衰,虛擬機(jī)將在當(dāng)前線程的棧幀中創(chuàng)建一個名為Lock Record空間,這個空間用于存儲當(dāng)前對象的Mark World拷貝饵骨,具體如下圖所示翘悉。
接著,虛擬機(jī)使用CAS嘗試將對象的對象頭Mark Wolrd指向Lock Record居触,也就是在Mark Wolrd的30bit存儲Lock Record的起始地址妖混,具體如下圖所示。如果上述操作執(zhí)行成功轮洋,當(dāng)前線程就持有了對象的鎖制市,此時對象處于輕量級鎖鎖定狀態(tài),對應(yīng)的鎖標(biāo)志位為00弊予。
如果上述操作執(zhí)行失敗祥楣,首先會檢查對象的對象頭Mark World是否指向了當(dāng)前線程棧幀中的Lock Record,如果指向了則表示當(dāng)前線程已經(jīng)持有了對象的鎖,否則表示對象的鎖已經(jīng)被其它線程持有误褪,鎖膨脹為重量級鎖责鳍,線程掛起等待。
輕量級鎖的釋放過程兽间,通過CAS將Lock Record中存儲的Mark Wolrd拷貝替換回對象的對象頭Mark Wolrd中历葛,替換成功則鎖釋放成功,否則表示有其它線程嘗試獲取過鎖嘀略,釋放鎖的同時恤溶,喚醒掛起的線程,這里筆者的理解是此時鎖膨脹為重量級鎖帜羊,喚醒等待線程競爭宏娄。
偏向鎖
鎖對象第一次被線程持有的時候,虛擬機(jī)通過CAS把獲取到這個鎖的線程ID記錄到對象頭Mark World中逮壁,操作成功則成功獲取偏向鎖,對象頭中的鎖標(biāo)志位設(shè)置為01粮宛。持有偏向鎖的線程每次執(zhí)行到這段同步代碼時窥淆,不需要任何同步操作,這項優(yōu)化稱為偏向鎖巍杈。
當(dāng)有其它線程嘗試獲取對象的鎖時忧饭,終止偏向模式,同時根據(jù)鎖是否處于鎖定狀態(tài)筷畦,撤銷偏向鎖恢復(fù)到未鎖定或輕量級鎖狀態(tài)词裤。
END
如果覺得有收獲,記得關(guān)注鳖宾、點贊吼砂、轉(zhuǎn)發(fā)。