探究synchronized底層原理(基於JAVA8原始碼分析)

dead_lee發表於2021-09-09

JVM支援方法級和方法內部一段指令序列的同步,都用同步鎖(monitor)實現

synchronized可以保證方法或者程式碼塊在執行時,同一時刻只有一個方法可以進入臨界區,同時它還可以保證共享變數的記憶體可見性

Java中每一個物件都可以作為鎖,這是synchronized實現同步的基礎
1. 普通同步方法,鎖是當前例項物件
2. 靜態同步方法,鎖是當前類的class物件
3. 同步方法塊,鎖是括號裡面的物件

當一個執行緒訪問同步程式碼塊

  • 首先得到鎖才能執行同步程式碼

  • 當退出或者拋異常時必須釋放鎖

如何來實現這個機制呢?

實現原理

圖片描述

看一段簡單的程式碼


圖片描述

生成的class檔案資訊

JVM的同步(synchronization)是monitor的進入和退出實現的,無論是顯式同步(有明確的monitorenter和monitorexit指令),還是隱式同步(無需透過位元組碼指令控制,依賴方法呼叫和返回指令實現)
Java中,同步用的最多的可能就是經synchronized修飾的同步方法
同步方法並不是用monitorenter和monitorexit實現的,而是由方法呼叫指令讀取執行時常量池中方法的ACC_SYNCHRONIZED標誌來隱式實現的0
.monitorentermonitorexit指令用於編譯synchronized同步0.語句塊

JVM從方法常量池中的方法表結構中的ACC_SYNCHRONIZED訪問標誌區區分是否為同步方法
呼叫時,呼叫指令將會檢查方法的ACC_SYNCHRONIZED是否設定,是則執行執行緒將先獲得同步鎖,然後執行方法,最後完成方法時釋放同步鎖
執行期間,其他執行緒都無法獲得同一個鎖,若執行期間拋異常並且方法內部無法處理時,所持有的鎖將在拋異常到同步方法之外時自動釋放

同步程式碼塊

monitorenter插入到同步程式碼塊的開始位置
monitorexit插入到結束位置
JVM需要保證每一個monitorenter都有一個monitorexit對應
任何物件都有一個monitor與之關聯,且當一個monitor被持有之後,他將處於鎖定狀態
執行緒執行到monitorenter時,將會嘗試獲取物件的monitor所有權,即嘗試獲取物件的鎖

同步方法

synchronized方法會被翻譯成普通的方法呼叫和返回指令如:invokevirtual、areturn指令
在JVM位元組碼層面並沒有任何特別的指令來實現被synchronized修飾的方法,
而是在Class檔案的方法表中將該方法的access_flags欄位中的synchronized標誌位 置1,表該方法是同步方法並使用呼叫該方法的物件或該方法所屬的Class在JVM的內部物件表示Klass做為鎖物件

深入之前我們需要了解兩個重要的概念:Java物件頭,Monitor。

Java物件頭

synchronized用的鎖存在Java物件頭
物件頭主要包括

  • Klass Pointer(型別指標)
    物件指向它的類後設資料的指標
    JVM透過該指標確定該物件是何類的例項

  • Mark Word(標記欄位)
    儲存物件的執行時資料
    是實現輕量級鎖和偏向鎖的關鍵

Mark Word

儲存物件自身的執行時資料,如雜湊碼、GC分代年齡、鎖狀態標誌、執行緒持有的鎖、偏向執行緒 ID、偏向時間戳等

Java物件頭一般佔有兩個機器碼(在32位虛擬機器中,1個機器碼等於4位元組,也就是32bit),但是如果物件是陣列型別,則需要三個機器碼,因為JVM虛擬機器可以透過Java物件的後設資料資訊確定Java物件的大小,但是無法從陣列的後設資料來確認陣列的大小,所以用一塊來記錄陣列長度


圖片描述

Java物件頭的儲存結構(32位VM)


物件頭資訊是與物件自身定義的資料無關的額外儲存成本,但是考慮到虛擬機器的空間效率,Mark Word被設計成一個非固定的資料結構以便在極小的空間記憶體儲存儘量多的資料,它會根據物件的狀態複用自己的儲存空間,也就是說,Mark Word會隨著程式的執行發生變化,變化狀態如下(32位虛擬機器):


圖片描述

Monitor

可以把它理解為一個同步工具,也可以描述為一種同步機制,它通常被描述為一個物件。
和萬物皆物件一樣,所有的Java物件是天生的Monitor,每一個Java物件都有成為Monitor的潛質,因為在Java的設計中 ,每一個Java物件自打孃胎裡出來就帶了一把看不見的鎖,它叫做內部鎖或者Monitor鎖
Monitor 是執行緒私有的資料結構,每一個執行緒都有一個可用monitor record列表,同時還有一個全域性的可用列表。
每一個被鎖住的物件都會和一個monitor關聯(物件頭的MarkWord中的LockWord指向monitor的起始地址),同時monitor中有一個Owner欄位存放擁有該鎖的執行緒的唯一標識,表示該鎖被這個執行緒佔用。其結構如下


圖片描述

Owner:初始時為NULL表示當前沒有任何執行緒擁有該monitor record,當執行緒成功擁有該鎖後儲存執行緒唯一標識,當鎖被釋放時又設定為NULL
EntryQ:關聯一個系統互斥鎖(semaphore),阻塞所有試圖鎖住monitor record失敗的執行緒
RcThis:表示blocked或waiting在該monitor record上的所有執行緒的個數
Nest:用來實現重入鎖的計數
HashCode:儲存從物件頭複製過來的HashCode值(可能還包含GC age)
Candidate:用來避免不必要的阻塞或等待執行緒喚醒,因為每一次只有一個執行緒能夠成功擁有鎖,如果每次前一個釋放鎖的執行緒喚醒所有正在阻塞或等待的執行緒,會引起不必要的上下文切換(從阻塞到就緒然後因為競爭鎖失敗又被阻塞)從而導致效能嚴重下降。Candidate只有兩種可能的值0表示沒有需要喚醒的執行緒1表示要喚醒一個繼任執行緒來競爭鎖。



作者:芥末無疆sss
連結:
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯絡作者獲得授權並註明出處。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4686/viewspace-2816191/,如需轉載,請註明出處,否則將追究法律責任。

相關文章