synchronized詳解

synchronized詳解

解釋

synchronized是jvm級別的一種重量級鎖,但是隨著jdk對synchronized的不斷優化,現在它已經變得沒有我們想象的那么重了。由于synchronized使用簡單,也不用手動釋放鎖,因此我們平時開發中用到最多的鎖就是它了。

synchronized鎖的三種形式

  • 普通方法:鎖是當前對象實例
  • 靜態方法:鎖是當前類的Class對象
  • 同步方法塊:鎖是synchronized括號里的對象

實現原理

同步代碼塊在編譯后會在前后分別插入monitorenter和monitorexit指令,每個對象在同一時刻只會與一個monitor相關聯,當線程執行到monitorenter指令時就會嘗試獲取對象所對應的monitor的所有權,如果這個monitor已經被其他線程獲取,則需要等待鎖釋放。

對象頭

synchronized鎖是存在對象頭中的。如果對象是數組類型,則虛擬機用3個字寬存儲對象頭,如果對象是非數組類型,則用2個字寬存儲對象頭。在32位虛擬機中,1字寬等于4字節,即32bit。

補充一點:Java對象保存在內存中,由三部分組成:對象頭、實例數據、對齊填充字節。Java頭由三部分組成:Mark Word、指向類的指針、數組長度(只有數組對象才有)。

32位jvm的Mark Word的存儲結構如下

鎖狀態 25bit 4bit 1bit是否偏向鎖 2bit鎖標志位
無鎖狀態 對象的hashCode 對象分代年齡 0 01

Mark Word中的數據隨著鎖標志位的變化而變化,如下

mark

鎖的升級

java1.6以后,為了減少獲取鎖和釋放鎖的性能消耗,引入了“偏向鎖”和”輕量級鎖“。鎖的狀態可以從無鎖狀態->偏向鎖->輕量級鎖->重量級鎖,隨著競爭情況逐漸升級,但是不能降級。

mark

偏向鎖

大多數情況下,鎖不僅不存在多線程競爭,而且總是由同一個線程多次獲得,為了讓線程獲得鎖的代價更低引入了偏向鎖。當一個線程訪問同步塊并獲取鎖時,會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程ID,以后該線程在進入和退出同步塊時不需要進行CAS操作來加鎖和解釋,只需要簡單地測試一下對象頭的Mark Word里是否存儲著指向當前線程的偏向鎖。如果是,則直接獲得鎖,執行同步塊;如果不是,則使用CAS操作更改線程ID,更改成功獲得鎖,更改失敗開始撤銷偏向鎖。

撤銷偏向鎖

偏向鎖只有存在鎖競爭的情況下才會釋放。撤銷偏向鎖需要等待全局安全點(在這個時間點上沒有正在執行的字節碼),首先暫停偏向鎖持有的線程,然后檢查此線程是否活著,如果線程不處于活動狀態,則轉成無鎖狀態;如果還活著,升級為輕量級鎖。下圖展示了偏向鎖的獲得與撤銷過程

mark

輕量級鎖

  • 加鎖:線程在執行同步塊之前,jvm會先在線程的棧幀中創建用于存儲鎖記錄的空間,然后將對象的Mark Word復制到鎖記錄中,官方稱Displaced Mark Word,再重試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,則獲取鎖;如果失敗,表示其他線程競爭鎖,當前線程使用自旋來獲取鎖。
  • 解鎖:輕量級鎖解鎖時,會使用CAS操作將Displaced Mark Word替換回對象頭中,如果成功,表示沒有競爭發生;如果失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖。下圖展示鎖膨脹流程圖。
mark

重量級鎖

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

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容