簡述
Java中每個對象都可以用來實現一個同步的鎖,這些鎖被稱為內置鎖(Intrinsic Lock)或監視器鎖(Monitor Lock)。
具體表現形式如下:
1、普通同步方法,鎖的是當前實例對象
2、靜態同步方法,鎖的是當前Class對象
3、對於同步代碼塊,鎖的是Synchronized括號中的代碼塊
線程在進入同步代碼塊之前會自動獲取鎖,並且在退出同步代碼塊時自動釋放鎖,無論是通過正常路徑退出,還是通過代碼塊中拋出異常退出。獲得內置鎖的唯一途徑就是進入由這個鎖保護的同步方法或代碼塊。
從 JVM 規范 中 可以 看到 Synchonized 在 JVM 里 的 實現 原理, JVM 基於 進入 和 退出 Monitor 對象 來 實現 方法 同步 和 代碼 塊 同步, 但 兩者 的 實現 細節 不一樣。 代碼 塊 同步 是 使用 monitorenter 和 monitorexit 指令 實現 的, 而 方法 同步 是 使用 另外 一種 方式 實現 的, 細節 在 JVM 規范 里 並沒有 詳細 說明。 但是, 方法 的 同步 同樣 可以 使用 這 兩個 指令 來 實現。
monitorenter 指令 是在 編譯 后 插入 到 同步 代碼 塊 的 開始 位置, 而 monitorexit 是 插入 到 方法 結束 處 和 異常 處, JVM 要 保證 每個 monitorenter 必須 有 對應 的 monitorexit 與之 配對。 任何 對象 都有 一個 monitor 與之 關聯, 當 且 一個 monitor 被 持有 后, 它將 處於 鎖定 狀態。 線程 執行 到 monitorenter 指令時, 將會 嘗試 獲取 對象 所 對應 的 monitor 的 所有權, 即 嘗試 獲得 對象 的 鎖。
對象的內存布局
在HotSpot虛擬機中,對象在內存中存儲的布局可以分為3塊區域:對象頭(Header)、實例數據(Instance Data)和對齊填充(Padding)。
synchronized 用的鎖 是 存在 對象頭 中。
如果 對象是數組類型, 則虛擬機用 3 個字 寬( Word) 存儲 對 象頭, 如果對象是非數組類型, 則用 2字寬存儲 對 象頭。 在 32位虛擬 機中, 1 字 寬 等於 4 字節, 即 32bit。
- HotSpot虛擬機的對象頭包括兩部分信息:
- Mark Word 第一部分用於存儲對象自身的運行時數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態標志、線程持有的鎖、偏向線程ID、偏向時間戳等,這部分數據的長度在32位和64位的虛擬機(未開啟壓縮指針)中分別為32bit和64bit。
- 類型指針 對象頭的另外一部分是類型指針,即對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。並不是所有的虛擬機實現都必須在對象數據上保留類型指針,換句話說,查找對象的元數據信息並不一定要經過對象本身,這點將在2.3.3節討論。
- 實例數據部分是對象真正存儲的有效信息,也是在程序代碼中所定義的各種類型的字段內容。無論是從父類繼承下來的,還是在子類中定義的,都需要記錄起來。這部分的存儲順序會受到虛擬機分配策略參數(FieldsAllocationStyle)和字段在Java源碼中定義順序的影響。
- 對齊填充並不是必然存在的,也沒有特別的含義,它僅僅起着占位符的作用
詳見 https://www.cnblogs.com/kaleidoscope/p/9699329.html