上一篇講了鎖與線程安全的關(guān)系,,講到jdk經(jīng)歷了幾個版本對鎖進行了優(yōu)化,這里簡單梳理一下鎖優(yōu)化,。 鎖升級過程就是鎖優(yōu)化在JDK最開始的時候synchronized屬于重量級的鎖,,每次加鎖都是通過操作系統(tǒng)來申請鎖,所以會造成synchronized的效率比較低,,尤其是隨著時代的發(fā)展,,多線程高并發(fā)越來越多,synchronized效率低的缺點就越來越明顯,,所以jdk對它進行了優(yōu)化,,不再是一開始就向操作系統(tǒng)申請鎖,分成偏向鎖 - 輕量級鎖 - 重量級鎖三個過程,。 要講鎖升級首先回顧一個知識點,,synchronized實際上是對對象加鎖的過程(鎖一段代碼則需指定對象,如果鎖方法,,普通方法鎖的是持有方法的對象,,靜態(tài)方法則是這個方法所在類的calss對象),在對象頭的mark word中最低的三位代表鎖狀態(tài),,其中1位是偏向鎖位兩位是普通鎖位,,具體如下圖:
這次主要關(guān)注mark word的后三位的變化,根據(jù)變化我們可以得出實際上對象的鎖狀態(tài)可以分成無鎖、偏向鎖,、輕量級鎖,、重量級鎖4個狀態(tài),GC過程中有對對象的鎖降級,,這個不是重點,,先不管了。 那么接下來就看看鎖是如何升級的,,首先最開始對象是無鎖狀態(tài),,當一個線程準備對這個對象加鎖前驗證這三個字節(jié)發(fā)現(xiàn)了無鎖狀態(tài),把對象是否偏向設置為1,,鎖標志位還是01,并把markword的線程ID改為當前線程ID,,此時對象處于偏向鎖狀態(tài),。 一個線程繼續(xù)對該該對象加鎖,發(fā)現(xiàn)是偏向鎖狀態(tài),,判斷偏向鎖線程是否是這個線程,,如果是則是直接進入,如果偏向線程不是當前線程,,也就是存在鎖競爭,,那么就撤銷偏向鎖,升級為輕量級鎖,。 輕量級鎖實現(xiàn)方式是各個線程在自己的線程棧生成LockRecord ,,用CAS操作將markword設置為指向自己這個線程的LockRecord的指針,設置成功者得到鎖,,沒有成功的將繼續(xù)使用CAS一直循環(huán)直到成功,,所以輕量級鎖也叫自旋鎖,JDK自旋有默認最大值是10次,,JDK6對自旋鎖進行了優(yōu)化,,自旋的時間不再是固定的,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態(tài)來決定的,,比如當前線程在剛剛成功獲取過自旋鎖,,那么虛擬機就會認為這次自旋也很有可能會成功,那么循環(huán)次數(shù)就可以多進行幾次,,這就叫自適應自旋,。有了自適應自旋就不用我們設置最大循環(huán)次數(shù),有JVM監(jiān)控動態(tài)設置,。 自旋鎖有個缺點就是等待的線程仍然在自旋運行,,如果自旋的次數(shù)太多或者自旋等待的線程太多會造成CPU消耗過大,這種情況反而不如向操作系統(tǒng)來申請鎖,阻塞其他線程,。所以在這種情況下鎖會升級成重量級鎖,,沒有獲取到鎖的現(xiàn)在直接在系統(tǒng)級別被掛起,直到系統(tǒng)釋放鎖喚醒這些掛起的線程,,這些線程再次搶鎖,。 鎖的升級過程畫了一個簡單的圖便于理解以上內(nèi)容,如下圖:
不重要的兩個鎖優(yōu)化還有兩個不重要的鎖優(yōu)化還是要了解了解的,。 第一個是鎖消除:當一段代碼中加了鎖,,但是通過JVM分析他是線程安全的,那么JVM會把鎖去掉,。比如方法中一段代碼有加鎖,,但是經(jīng)過分析不會出現(xiàn)線程安全的問題,那么JVM就會把鎖給消除,。 第二個是鎖粗化:當JVM檢測到一段連續(xù)的多次操作都在對同一個對象多次加鎖,,那么JVM可能會優(yōu)化成對這整段加一個鎖,沒有把加鎖的操作分的那么細,,所以叫鎖粗化,。 具體代碼案例如下圖: 總結(jié)鎖升級主要分為偏向鎖 - 輕量級鎖 - 重量級鎖三層,偏向鎖,、輕量級鎖是在Java內(nèi)部的優(yōu)化,,屬于所謂的用戶態(tài),而重量級鎖則是向操作系統(tǒng)申請,,屬于內(nèi)核態(tài),。在鎖競爭不激烈的時候由jvm自己解決肯定性能是最好的,但是jvm通過自旋方式解決會消耗CPU性能,,所以在鎖競爭激烈的情況下重量級鎖性能更好,。 鎖升級是機制層面的優(yōu)化,而鎖消除和鎖粗化則是jvm對代碼層面的優(yōu)化,。 Java程序員日常學習筆記,,如理解有誤歡迎各位交流討論! |
|
來自: IT樂知 > 《程序員的私房筆記》