Java並發之鎖升級:無鎖->偏向鎖->輕量級鎖->重量級鎖


Java並發之鎖升級:無鎖->偏向鎖->輕量級鎖->重量級鎖

對象頭markword

在lock_bits為01的大前提下,只有當是否偏向鎖位值為1的時候,才表明當前對象處於偏向鎖定狀態;為0時,表明該對象被hash計算了

如果線程獲得該偏向鎖,則對象頭是否偏向鎖置1,標志位為01。

鎖的升級過程——無鎖->偏向鎖->輕量級鎖->重量級鎖

明確 Java 線程切換的代價,是理解java中各種鎖的優缺點的基礎之一。

JDK1.6以前synchronized 關鍵字只表示重量級鎖,1.6之后區分為偏向鎖、輕量級鎖、重量級鎖。

偏向鎖,輕量級鎖,重量級鎖分別解決三個問題

  • 只有一個線程進入臨界區
  • 多個線程交替進入臨界區
  • 多個線程同時進入臨界區

偏向鎖:

為什么引入偏向鎖

​ HotSpot的作者大量的研究發現,大多數時候是不存在鎖競爭的,常常是一個線程多次獲得同一個鎖,因此如果每次都要競爭鎖會增大很多沒有必要付出的代價,為了降低獲取鎖的代價,才引入的偏向鎖。

​ Synchronized 是通過對象內部的一個叫做監視器鎖(monitor)來實現的。但是監視器鎖本質又是依賴於底層的操作系統的 Mutex Lock 來實現的。而操作系統實現線程之間的切換這就需要從用戶態轉換到核心態,這個成本非常高,狀態之間的轉換需要相對比較長的時間,這就是為什么 Synchronized 效率低的原因。因此,這種依賴於操作系統 Mutex Lock 所實現的鎖我們稱之為“重量級鎖”。JDK 中對 Synchronized 做的種種優化,其核心都是為了減少這種重量級鎖的使用。JDK1.6 以后,為了減少獲得鎖和釋放鎖所帶來的性能消耗,提高性能,引入了自旋鎖、自適應自旋鎖、輕量級鎖和偏向鎖。

輕量級鎖

輕量級鎖
輕量級是相對於使用操作系統互斥量來實現的傳統鎖而言的。但是,首先需要強調一點的是,輕量級鎖並不是用來代替重量級鎖的,它的本意是在沒有多線程競爭的前提下,減少傳統的重量級鎖使用產生的性能消耗。在解釋輕量級鎖的執行過程之前,先明白一點,輕量級鎖所適應的場景是線程交替執行同步塊的情況,如果存在同一時間訪問同一鎖的情況,就會導致輕量級鎖膨脹為重量級鎖。

重量級鎖

​ 內置鎖在Java中被抽象為監視器鎖(monitor)。在JDK 1.6之前,監視器鎖可以認為直接對應底層操作系統中的互斥量(mutex)。這種同步方式的成本非常高,包括系統調用引起的內核態與用戶態切換、線程阻塞造成的線程切換等。因此,后來稱這種鎖為“重量級鎖”。

無鎖->偏向鎖

​ 當一個線程訪問同步塊並 獲取鎖時,會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程ID,以后該線程在進入和退出 同步塊時不需要進行CAS操作來加鎖和解鎖,只需簡單地測試一下對象頭的Mark Word里是否 存儲着指向當前線程的偏向鎖。如果測試成功,表示線程已經獲得了鎖。如果測試失敗,則需 要再測試一下Mark Word中偏向鎖的標識是否設置成1(表示當前是偏向鎖):如果沒有設置,則 使用CAS競爭鎖;如果設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。

​ 偏向鎖使用了一種等到競爭出現才釋放鎖的機制,所以當其他線程嘗試競爭偏向鎖時, 持有偏向鎖的線程才會釋放鎖。偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有正在執行的字節碼)。它會首先暫停擁有偏向鎖的線程,然后檢查持有偏向鎖的線程是否活着, 如果線程不處於活動狀態,則將對象頭設置成無鎖狀態;如果線程仍然活着,擁有偏向鎖的棧 會被執行,遍歷偏向對象的鎖記錄,棧中的鎖記錄和對象頭的Mark Word要么重新偏向於其他 線程,要么恢復到無鎖或者標記對象不適合作為偏向鎖,最后喚醒暫停的線程。

  • 當沒有競爭出現時,默認會使用偏向鎖。JVM 會利用 CAS 操作(compare and swap),在對象頭上的 Mark Word 部分設置線程 ID,以表示這個對象偏向於當前線程,所以並不涉及真正的互斥鎖。這樣做的假設是基於在很多應用場景中,大部分對象生命周期中最多會被一個線程鎖定,使用偏向鎖可以降低無競爭開銷。
  • 如果有另外的線程試圖鎖定某個已經被偏向過的對象,JVM 就需要撤銷(revoke)偏向鎖,並切換到輕量級鎖實現。輕量級鎖依賴 CAS 操作 Mark Word 來試圖獲取鎖,如果重試成功,就使用輕量級鎖;否則,進一步升級為重量級鎖

偏向鎖的獲得和撤銷流程

輕量級鎖膨脹

​ 線程在執行同步塊之前,JVM會先在當前線程的棧楨中創建用於存儲鎖記錄的空間,並 將對象頭中的Mark Word復制到鎖記錄中,官方稱為Displaced Mark Word。然后線程嘗試使用 CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當前線程獲得鎖,如果失敗,表示其他線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。輕量級鎖解鎖 輕量級解鎖時,會使用原子的CAS操作將Displaced Mark Word替換回到對象頭,如果成功,則表示沒有競爭發生。如果失敗表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。

總結

可以理解為:

當只有一個線程時,線程占有鎖時是偏向鎖;當有其他線程競爭鎖時,偏向鎖升級為輕量級鎖;當多線程不能交替獲得輕量級鎖而不產生競爭(可以自旋等待)時,輕量級鎖升級為重量級鎖。

參考:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM