經(jīng)常聽到人家說什么偏向鎖、輕量級鎖刹衫、重量級鎖醋寝、自旋鎖搞挣,自適應(yīng)自旋鎖。每當(dāng)這個時候音羞,以前的我是一臉懵逼…
那到底什么是偏向鎖囱桨、輕量級鎖、自旋鎖和重量級鎖嗅绰?
首先我們需要明確一個概念舍肠,所有的這些鎖都是針對JVM的內(nèi)容對象鎖。是synchronized實現(xiàn)里相關(guān)的東西窘面。所以翠语,要了解這些鎖,我們必須要知道synchronized在JVM里的實現(xiàn)原理财边。
JVM基于進(jìn)入和退出Monitor對象來實現(xiàn)synchronized衬衬。在synchronized代碼塊開始位置織入monitorenter,在結(jié)束的位置(正常結(jié)束和異常結(jié)束處)織入monitorexit指令耿币。線程執(zhí)行到monitorenter處身堡,會獲取鎖對象鎖對應(yīng)的monitor的所有權(quán)侯养,即嘗試獲得對象的鎖。(任意對象都有一個monitor與之關(guān)聯(lián)鲸鹦,當(dāng)且一個monitor被持有后慧库,他處于鎖定狀態(tài))。
一馋嗜、對象里存了什么東西
既然是對象鎖齐板,那我肯定要知道對象是啥。
那java的對象結(jié)構(gòu)到底是什么樣子的呢葛菇?我們看下Hotspot JVM中甘磨,32位機(jī)器下,對象的存儲內(nèi)容眯停。
這個圖一定要仔細(xì)看下济舆。我們有時候也把第一個32位也叫MarkWord。
比如一個Integer對象莺债,實際大小是:4(MarkWord)+4(對象地址)+4(實際int值)+4(補全的對齊)滋觉。所以,下次人家問一個Integer占多少字節(jié)的時候齐邦,勇敢的說出來:16個字節(jié)椎侠,int的4倍。
二措拇、鎖的狀態(tài)和升級
從上面的圖中的MarkWord,我們可以知道一個對象有4種鎖的狀態(tài)我纪,由低到高依次為:無鎖狀態(tài)。偏向鎖、輕量級鎖浅悉、重量級鎖趟据,其實這些都是鎖的狀態(tài)。并且JVM還規(guī)定鎖的等級只可以升級仇冯,不可以降級之宿。這種鎖升級卻不能降級的策略族操,目的是為了提高獲得鎖和釋放鎖的效率苛坚。
三、偏向鎖
首先為什么要有偏向鎖色难?
偏向鎖是為了在無鎖競爭的情況下泼舱,避免在輕量級鎖獲取過程中執(zhí)行不必要的CAS原子指令,因為CAS原子指令雖然相對于重量級鎖來說開銷比較小但還是存在非臣侠颍可觀的本地延遲娇昙。
那偏向鎖是怎么做到減少CAS的原子指令的呢?
我們來看下偏向鎖的獲取和釋放流程:
1笤妙、線程A獲得鎖冒掌,會在線程A的的棧幀里創(chuàng)建lock record(鎖記錄變量),則在鎖對象的對象頭里和lock record里存儲線程A的線程id.以后該線程的進(jìn)入蹲盘,就不需要CAS操作股毫,只需要判斷是否是當(dāng)前線程。
2召衔、線程A獲取鎖后铃诬,不會主動釋放鎖。直到線程B也要競爭該鎖時苍凛,線程A才會釋放鎖趣席。
偏向鎖的釋放,需要等待全局安全點(在這個時間點上沒有正在執(zhí)行的字節(jié)碼)醇蝴,它會首先暫停擁有偏向鎖的線程宣肚,然后檢查持有偏向鎖的線程是否還活著,如果線程不處于活動狀態(tài)悠栓,則將對象頭設(shè)置成無鎖狀態(tài)霉涨。如果線程仍然活著,擁有偏向鎖的棧會被執(zhí)行闸迷,遍歷偏向?qū)ο蟮乃涗浨陡佟械逆i記錄和對象頭的Mark Word要么重新偏向其他線程,要么恢復(fù)到無鎖腥沽,或者標(biāo)記對象不適合作為偏向鎖逮走。最后喚醒暫停的線程。
JVM怎么開啟/關(guān)閉偏向鎖今阳?
偏向鎖在jdk1.6之后是默認(rèn)開啟的师溅。
通過jvm的參數(shù)-XX:-UseBiasedLocking,可以關(guān)閉偏向鎖茅信,然后默認(rèn)會進(jìn)入輕量級鎖。
通過jvm的參數(shù)-XX:+UseBiasedLocking
-XX:BiasedLockingStartupDelay=0墓臭,可以開啟偏向鎖蘸鲸,并且設(shè)置偏向鎖的啟動延遲為0(這個值默認(rèn)為5秒,因為jvm認(rèn)為系統(tǒng)剛剛啟動的時候資源競爭是很激烈的窿锉。我們把這個值改為0酌摇,方便測試。)
我們可以來看一下嗡载,開啟和關(guān)閉偏向鎖之后窑多,系統(tǒng)的差距
我們分別用兩個參數(shù)來運行這段代碼:
1、關(guān)閉偏向鎖洼滚。-XX:-UseBiasedLocking
運行結(jié)果如下:
2埂息、開啟偏向鎖。-XX:+UseBiasedLocking-XX:BiasedLockingStartupDelay=0
通過這個結(jié)果遥巴,我們可以大概的得出結(jié)論:在本例子中千康,完全無鎖的情況下,開啟偏向鎖比關(guān)閉偏向鎖铲掐,性能提升了5%到10%拾弃。
四、輕量級鎖
輕量級鎖(Lightweight Locking)本意是為了減少多線程進(jìn)入互斥的幾率迹炼,并不是要替代互斥砸彬。它利用CAS嘗試在進(jìn)入互斥前,進(jìn)行補救,避免調(diào)用操作系統(tǒng)層面的重量級互斥鎖斯入。
線程A獲得鎖砂碉,會在線程A的棧幀里創(chuàng)建lock
record(鎖記錄變量),讓lock record的指針指向鎖對象的對象頭中的mark word.再讓mark word指向lock record.這就是獲取了鎖刻两。
輕量級鎖增蹭,線程B在鎖競爭時,發(fā)現(xiàn)鎖已經(jīng)被線程A占用磅摹,則線程B不進(jìn)入內(nèi)核態(tài)滋迈,讓線程B自旋(自旋的事情我們后面再講),執(zhí)行空循環(huán)户誓,等待線程A釋放鎖饼灿。如果,完成自旋策略還是發(fā)現(xiàn)線程A沒有釋放鎖帝美,或者讓線程C占用了碍彭。則線程B試圖將輕量級鎖升級為重量級鎖。
五、重量級鎖
重量級鎖庇忌,就是讓爭搶鎖的線程從用戶態(tài)轉(zhuǎn)換成內(nèi)核態(tài)舞箍,讓CPU借助操作系統(tǒng)進(jìn)行線程協(xié)調(diào)。
六皆疹、自旋鎖
前面在講輕量級鎖的時候疏橄,我們已經(jīng)提到了自旋。本質(zhì)就是不讓出CPU略就,執(zhí)行空循環(huán)捎迫,然后等待拿鎖。
使用自旋鎖后残制,線程被掛起的幾率相對減少立砸,線程執(zhí)行的連貫性相對加強(qiáng)掖疮。因此初茶,對于那些鎖競爭不是很激烈,鎖占用時間很短的并發(fā)線程浊闪,具有一定的積極意義恼布。但是,對于鎖競爭激烈搁宾,單線程鎖占用很長時間的并發(fā)程序折汞,自旋鎖在自旋等待后,如果依然無法獲得對應(yīng)的鎖盖腿,反而浪費了系統(tǒng)的資源爽待。
我們來看下自己寫的一個自旋鎖。
在JDK1.6中翩腐,Java虛擬機(jī)提供-XX:+UseSpinning參數(shù)來開啟自旋鎖鸟款,使用-XX:PreBlockSpin參數(shù)來設(shè)置自旋鎖等待的次數(shù),默認(rèn)是10次茂卦。
在JDK1.7開始何什,自旋鎖的參數(shù)被取消,虛擬機(jī)不再支持由用戶配置自旋鎖等龙,自旋鎖總是會執(zhí)行处渣,自旋鎖次數(shù)也由虛擬機(jī)自動調(diào)整,也就是開始使用自適應(yīng)自旋鎖蛛砰。
七罐栈、自適應(yīng)自旋鎖
自適應(yīng)意味著自旋的次數(shù)或者時間不再是固定的,而是由前一次在同一個鎖上的自旋時間以及鎖擁有者的狀態(tài)來決定泥畅。如果在同一個鎖對象上,自旋等待剛好成功獲得鎖荠诬, 并且在持有鎖的線程在運行中,那么虛擬機(jī)就會認(rèn)為這次自旋也是很有可能獲得鎖, 進(jìn)而它將允許自旋等待相對更長的時間浅妆。
總結(jié):
這些東西可能在寫代碼的時候完全用不到望迎,正常情況下,只需要寫個synchronized就完事了凌外。但是辩尊,在虛擬機(jī)調(diào)優(yōu)方面還是很有用的。當(dāng)然康辑,面試的時候也很有用摄欲。
參考資料:
http://www.cnblogs.com/shangxiaofei/p/5569879.html