偏向鎖,輕量級鎖與重量級鎖的區別與膨脹
一直被這三個鎖的膨脹問題所困擾,不知道到底實在什麼時候會有偏向鎖升級到輕量級鎖,什麼時候由輕量級鎖升級到重量級鎖。找到好久,也沒有找到簡潔明瞭的答案。
綜合多個方面的描述綜合自己的理解,特地記錄下來。但是也有可能理解有誤。
先依然描述這三個鎖是什麼: 這裡直接從《深入理解Java虛擬機器》貼上過來。
偏向鎖
Hotspot 的作者經過以往的研究發現大多數情況下鎖不僅不存在多執行緒競爭,而且總是由同一執行緒多次獲得,為了讓執行緒獲得鎖的代價更低而引入了偏向鎖。當一個執行緒訪問同步塊並獲取鎖時,會在物件頭和棧幀中的鎖記錄裡儲存鎖偏向的執行緒 ID,以後該執行緒在進入和退出同步塊時不需要花費
CAS操作來加鎖和解鎖,而只需簡單的測試一下物件頭的Mark Word裡是否儲存著指向當前執行緒的偏向鎖,如果測試成功,表示執行緒已經獲得了鎖,
如果測試失敗,則需要再測試下 Mark Word中偏向鎖的標識是否設定成 1(表示當前是偏向鎖),如果沒有設定,則使用 CAS 競爭鎖,如果設定了,
則嘗試使用 CAS 將物件頭的偏向鎖指向當前執行緒。
偏向鎖的撤銷:偏向鎖使用了一種等到競爭出現才釋放鎖的機制,所以當其他執行緒嘗試競爭偏向鎖時,持有偏向鎖的執行緒才會釋放鎖。偏向鎖的撤
銷,需要等待全域性安全點(在這個時間點上沒有位元組碼正在執行),它會首先暫停擁有偏向鎖的執行緒,然後檢查持有偏向鎖的執行緒是否活著,如果執行緒
不處於活動狀態,則將物件頭設定成無鎖狀態,如果執行緒仍然活著,擁有偏向鎖的棧會被執行,遍歷偏向物件的鎖記錄,棧中的鎖記錄和物件頭的
Mark Word要麼重新偏向於其他執行緒,要麼恢復到無鎖或者標記物件不適合作為偏向鎖,最後喚醒暫停的執行緒。
輕量級鎖
輕量級鎖加鎖:執行緒在執行同步塊之前, JVM會先在當前執行緒的棧楨中建立用於儲存鎖記錄的空間,並將物件頭中的Mark Word複製到鎖記錄中,官方稱為Displaced Mark Word。然後執行緒嘗試使用 CAS 將物件頭中的Mark Word替換為指向鎖記錄的指標。如果成功,當前執行緒獲得鎖,如果失敗,
表示其他執行緒競爭鎖,當前執行緒便嘗試使用自旋來獲取鎖。
的狀態值變為”10”,Mark Word中儲存的就是指向重量級(互斥量)的指標。
輕量級鎖解鎖:輕量級解鎖時,會使用原子的 CAS 操作來將Displaced Mark Word替換回到物件頭,如果成功,則表示沒有競爭發生。如果失敗,
表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。
上面那句標紅的話,真的是困擾了我好久,到底是兩個執行緒,還是三個執行緒才開始膨脹呢!
下面是我個人自己的理解:
偏向所鎖,輕量級鎖都是樂觀鎖,重量級鎖是悲觀鎖。
一個物件剛開始例項化的時候,沒有任何執行緒來訪問它的時候。它是可偏向的,意味著,它現在認為只可能有一個執行緒來訪問它,所以當第一個
執行緒來訪問它的時候,它會偏向這個執行緒,此時,物件持有偏向鎖。偏向第一個執行緒,這個執行緒在修改物件頭成為偏向鎖的時候使用CAS操作,並將
物件頭中的ThreadID改成自己的ID,之後再次訪問這個物件時,只需要對比ID,不需要再使用CAS在進行操作。
一旦有第二個執行緒訪問這個物件,因為偏向鎖不會主動釋放,所以第二個執行緒可以看到物件時偏向狀態,這時表明在這個物件上已經存在競爭了,檢查原來持有該物件鎖的執行緒是否依然存活,如果掛了,則可以將物件變為無鎖狀態,然後重新偏向新的執行緒,如果原來的執行緒依然存活,則馬上執行那個執行緒的操作棧,檢查該物件的使用情況,如果仍然需要持有偏向鎖,則偏向鎖升級為輕量級鎖,(偏向鎖就是這個時候升級為輕量級鎖的)。如果不存在使用了,則可以將物件回覆成無鎖狀態,然後重新偏向。
輕量級鎖認為競爭存在,但是競爭的程度很輕,一般兩個執行緒對於同一個鎖的操作都會錯開,或者說稍微等待一下(自旋),另一個執行緒就會釋放鎖。 但是當自旋超過一定的次數,或者一個執行緒在持有鎖,一個在自旋,又有第三個來訪時,輕量級鎖膨脹為重量級鎖,重量級鎖使除了擁有鎖的執行緒以外的執行緒都阻塞,防止CPU空轉。