偏向鎖
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替換回到對象頭,如果成功,則表示沒有競爭發生。如果失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。
重量級鎖
重量級鎖是 Java 虛擬機中最為基礎的鎖實現。在這種狀態下,Java 虛擬機會阻塞加鎖失敗的線程,並且在目標鎖被釋放的時候,喚醒這些線程。
上面那句標紅的話,真的是困擾了我好久,到底是兩個線程,還是三個線程才開始膨脹呢!
下面是我個人自己的理解:
偏向所鎖,輕量級鎖都是樂觀鎖,重量級鎖是悲觀鎖。
一個對象剛開始實例化的時候,沒有任何線程來訪問它的時候。它是可偏向的,意味着,它現在認為只可能有一個線程來訪問它,所以當第一個
線程來訪問它的時候,它會偏向這個線程,此時,對象持有偏向鎖。偏向第一個線程,這個線程在修改對象頭成為偏向鎖的時候使用CAS操作,並將
對象頭中的ThreadID改成自己的ID,之后再次訪問這個對象時,只需要對比ID,不需要再使用CAS在進行操作。
一旦有第二個線程訪問這個對象,因為偏向鎖不會主動釋放,所以第二個線程可以看到對象時偏向狀態,這時表明在這個對象上已經存在競爭了,檢查原來持有該對象鎖的線程是否依然存活,如果掛了,則可以將對象變為無鎖狀態,然后重新偏向新的線程,如果原來的線程依然存活,則馬上執行那個線程的操作棧,檢查該對象的使用情況,如果仍然需要持有偏向鎖,則偏向鎖升級為輕量級鎖,(偏向鎖就是這個時候升級為輕量級鎖的)。如果不存在使用了,則可以將對象回復成無鎖狀態,然后重新偏向。
輕量級鎖認為競爭存在,但是競爭的程度很輕,一般兩個線程對於同一個鎖的操作都會錯開,或者說稍微等待一下(自旋),另一個線程就會釋放鎖。 但是當自旋超過一定的次數,或者一個線程在持有鎖,一個在自旋,又有第三個來訪時,輕量級鎖膨脹為重量級鎖,重量級鎖使除了擁有鎖的線程以外的線程都阻塞,防止CPU空轉。