1. 程式人生 > >xad707348125的專欄

xad707348125的專欄

偏向鎖   http://blog.163.com/[email protected]/blog/static/35971862201472274958280/
Java偏向鎖(Biased Locking)是Java6引入的一項多執行緒優化。它通過消除資源無競爭情況下的同步原語,進一步提高了程式的執行效能。
偏向鎖,顧名思義,它會偏向於第一個訪問鎖的執行緒,如果在接下來的執行過程中,該鎖沒有被其他的執行緒訪問,則持有偏向鎖的執行緒將永遠不需要觸發同步。
如果在執行過程中,遇到了其他執行緒搶佔鎖,則持有偏向鎖的執行緒會被掛起,JVM會嘗試消除它身上的偏向鎖,將鎖恢復到標準的輕量級鎖。(偏向鎖只能在單執行緒下起作用)
因此 流程是這樣的 偏向鎖->輕量級鎖->重量級鎖 鎖存在Java物件頭裡。如果物件是陣列型別,則虛擬機器用3個Word(字寬)儲存物件頭,如果物件是非陣列型別,則用2字寬儲存物件頭。在32位虛擬機器中,一字寬等於四位元組,即32bit。 機制:每個鎖都關聯一個請求計數器和一個佔有他的執行緒,當請求計數器為0時,這個鎖可以被認為是unhled的,當一個執行緒請求一個unheld的鎖時,JVM記錄鎖的擁有者,並把鎖的請求計數加1,如果同一個執行緒再次請求這個鎖時,請求計數器就會增加,當該執行緒退出syncronized塊時,計數器減1,當計數器為0時,鎖被釋放(這就保證了鎖是可重入的,不會發生死鎖的情況)。
偏向鎖,簡單的講,就是在鎖物件的物件頭中有個ThreaddId欄位,這個欄位如果是空的,第一次獲取鎖的時候,就將自身的ThreadId寫入到鎖的ThreadId欄位內,將鎖頭內的是否偏向鎖的狀態位置1.這樣下次獲取鎖的時候,直接檢查ThreadId是否和自身執行緒Id一致,如果一致,則認為當前執行緒已經獲取了鎖,因此不需再次獲取鎖,略過了輕量級鎖和重量級鎖的加鎖階段。提高了效率。 但是偏向鎖也有一個問題,就是當鎖有競爭關係的時候,需要解除偏向鎖,使鎖進入競爭的狀態。 具體圖片如下: java 偏向鎖 - silver9886@126 - silver9886@126的部落格 偏向鎖的搶佔,其實就是兩個程序對鎖的搶佔,在synchrnized鎖下表現為輕量鎖方式進行搶佔。 java 偏向鎖 - silver9886@126 - silver9886@126的部落格    注意這時只是偏向鎖的釋放,之後會進入到輕量級鎖階段,兩個執行緒進入鎖競爭狀態,一個具體例子可以參考synchronized鎖機制。 注:synchronized鎖流程如下(轉自http://xly1981.iteye.com/blog/1766224): 第一步,檢查MarkWord裡面是不是放的自己的ThreadId ,如果是,表示當前執行緒是處於 “偏向鎖” 

第二步,如果MarkWord不是自己的ThreadId,鎖升級,這時候,用CAS來執行切換,新的執行緒根據MarkWord裡面現有的ThreadId,通知之前執行緒暫停,之前執行緒將Markword的內容置為空。 
第三步,兩個執行緒都把物件的HashCode複製到自己新建的用於儲存鎖的記錄空間,接著開始通過CAS操作,把共享物件的MarKword的內容修改為自己新建的記錄空間的地址的方式競爭MarkWord, 
第四步,第三步中成功執行CAS的獲得資源,失敗的則進入自旋 
第五步,自旋的執行緒在自旋過程中,成功獲得資源(即之前獲的資源的執行緒執行完成並釋放了共享資源),則整個狀態依然處於 輕量級鎖的狀態,如果自旋失敗  第六步,進入重量級鎖的狀態,這個時候,自旋的執行緒進行阻塞,等待之前執行緒執行完成並喚醒自己 總結: 偏向鎖,其實是無鎖競爭下可重入鎖的簡單實現 參考資料: 1.http://xly1981.iteye.com/blog/1766224 2.Java SE1.6中的Synchronized  http://www.dedecms.com/knowledge/program/jsp-java/2012/0808/4386.html 3.Java偏向鎖實現原理 http://kenwublog.com/theory-of-java-biased-locking

輕量鎖

http://blog.sina.com.cn/s/blog_c038e9930102v2ht.html

輕量級鎖也是一種多執行緒優化,它與偏向鎖的區別在於,輕量級鎖是通過CAS來避免進入開銷較大的互斥操作,而偏向鎖是在無競爭場景下完全消除同步,連CAS也不執行(CAS本身仍舊是一種作業系統同步原語,始終要在JVM與OS之間來回,有一定的開銷)。

輕量級鎖(LightweightLocking)本意是為了減少多執行緒進入互斥的機率,並不是要替代互斥。

它利用了CPU原語Compare-And-Swap(CAS,彙編指令CMPXCHG),嘗試在進入互斥前,進行補救

輕量級鎖加鎖:執行緒在執行同步塊之前,JVM會先在當前執行緒的棧楨中建立用於儲存鎖記錄的空間,並將物件頭中的MarkWord複製到鎖記錄中,官方稱為DisplacedMark Word。然後執行緒嘗試使用CAS將物件頭中的MarkWord替換為指向鎖記錄的指標。如果成功,當前執行緒獲得鎖,如果失敗,表示其他執行緒競爭鎖,當前執行緒便嘗試使用自旋獲取鎖

輕量級鎖解鎖:輕量級解鎖時,會使用原子的CAS操作來將DisplacedMark Word替換回到物件頭,如果成功,則表示沒有競爭發生。如果失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。下圖是兩個執行緒同時爭奪鎖,導致鎖膨脹的流程圖。

 Java鎖Synchronized之輕量鎖

Java鎖Synchronized之輕量鎖

 在圖中,提到了拷貝鎖物件頭的objectmark word,由於脫離了原始markword,官方將它冠以displaced字首,即displacedmark word(置換標記字)。

這個displacedmark word是整個輕量級鎖實現的關鍵,在CAS中的compare就需要用它作為條件。

為什麼要拷貝mark word?其實很簡單,原因是為了不想在lock與unlock這種底層操作上再加同步。

在拷貝完object markword之後,JVM做了一步交換指標的操作,即流程中第一個橙色矩形框內容所述。

============

將object mark word裡的輕量級鎖指標指向lockrecord所在的stack指標,作用是讓其他執行緒知道,該objectmonitor已被佔用(就像偏向鎖中用CAS的方式將markword的id指向當前嘗試獲取鎖的執行緒id,這裡是將markword中的輕量級鎖指標以CAS的方式嘗試指向當前執行緒的lockrecord,這樣別的執行緒便知道當前輕量鎖已經指向別的執行緒了)。

lock record裡的owner指標指向objectmark word的作用是為了在接下里的執行過程中,識別哪個物件被鎖住了

CAS 的是makeworld中的輕量級鎖指標

================

下圖直觀地描述了交換指標的操作。

Java鎖Synchronized之輕量鎖

  Java鎖Synchronized之輕量鎖

最後一步unlock中,我們發現,JVM同樣使用了CAS來驗證object markword在持有鎖到釋放鎖之間,有無被其他執行緒訪問。

如果其他執行緒在持有鎖這段時間裡,嘗試獲取過鎖,則可能自身被掛起,而markword的重量級鎖指標也會被相應修改

釋放鎖執行緒視角所以由輕量鎖切換到重量鎖,是發生在輕量鎖釋放鎖的期間,之前在獲取鎖的時候它拷貝了鎖物件頭的markword,在釋放鎖的時候如果它發現在它持有鎖的期間有其他執行緒來嘗試獲取鎖了,並且該執行緒對markword做了修改,兩者比對發現不一致,則切換到重量鎖。

因為重量級鎖被修改了,所有display mark word和原來的markword不一樣了。

怎麼補救,就是進入mutex前,compare一下obj的markword狀態。確認該markword是否被其他執行緒持有。

此時如果執行緒已經釋放了markword,那麼通過CAS後就可以直接進入執行緒,無需進入mutex,就這個作用。

嘗試獲取鎖執行緒視角:如果執行緒嘗試獲取鎖的時候,輕量鎖正被其他執行緒佔有,那麼它就會修改markword修改重量級鎖,表示該進入重量鎖了。

還有一個注意點:等待輕量鎖的執行緒不會阻塞,它會一直自旋等待鎖,並如上所說修改markword

這就是自旋鎖,嘗試獲取鎖的執行緒,在沒有獲得鎖的時候,不被掛起,而轉而去執行一個空迴圈,即自旋。在若干個自旋後,如果還沒有獲得鎖,則才被掛起,獲得鎖,則執行程式碼。

因為自旋會消耗CPU,為了避免無用的自旋(比如獲得鎖的執行緒被阻塞住了),一旦鎖升級成重量級鎖,就不會再恢復到輕量級鎖狀態。當鎖處於這個狀態下,其他執行緒試圖獲取鎖時,都會被阻塞住,當持有鎖的執行緒釋放鎖之後會喚醒這些執行緒,被喚醒的執行緒就會進行新一輪的奪鎖之爭。