synchronized原理分析筆記

使用場景

  • 使用場景分爲兩種,一種是用於類,一種是用於對象;
  • 類: 修飾靜態方法、同步塊指向類;修飾方法指向當前類,同步塊指向指定的類
  • 對象: 修飾非靜態方法、同步塊指向對象;修飾方法指向當前實例,同步塊指向指定的對象
  • 每個對象和類都有自己獨立的監聽器Monitor,jvm用Monitor實現Lock;比如HashTable.class 有一個Monitor,每個new HashTable() 也有自己Monitor;
  • 監聽器Monitor同一時間只能有一個線程持有,也就是說一個對象的某個同步方法或同步塊被任意線程訪問時,改對象的所有同步方法和同步塊都會阻塞

鎖的升級(膨脹)

  • jdk6之後引入了偏向鎖、輕量級鎖,就有了4種狀態(無鎖、偏向鎖、輕量級鎖、重量級鎖);
  • 鎖在被持有時纔會膨脹改變對象頭中狀態,一旦釋放後會狀態重置
  • 鎖狀態存儲在對象頭 (下圖是32位系統的【對象頭結構】)
    對象頭結構
  • 無鎖(禁用偏向)

    對象頭內容:偏向標誌(0)、標誌位(01)、hashCode(生成過hashCode的對象有值);
    未開啓偏向鎖時創建的對象、生成過hashCode的對象、數組對象都是無鎖(禁用偏向)狀態​
    jvm默認開啓偏向鎖,但是延遲開啓默認4秒,所以默認前4秒創建的對象不可偏向;可以通過 -XX:BiasedLockingStartupDelay=0改爲零延時(ms)
    禁用偏向的對象鎖升級時直接到輕量級鎖
    調用Object.hashCode() 或System.identityHashCode()生成hashCode; PS:第一次調用時纔會生成,後面再調用直接從對象中獲取

  • 偏向鎖

    對象頭內容:偏向標誌(1)、標誌位(01)、偏向線程ID、時間戳;初始未偏向任何線程時不記錄任何線程ID;
    當某個線程嘗試執行同步代碼時,如果對象記錄的線程ID與之相同直接執行,否則使用CAS修改線程ID,修改失敗則升級爲輕量級鎖
    偏向鎖狀態下,對象生成hashCode,會直接升級爲重量級鎖

  • 輕量級鎖

    對象頭內容:偏向標誌(0)、標誌位(00)、Lock Record的指針;
    當某個線程嘗試執行同步代碼時,如果爲無鎖狀態,創建Lock Record並copy mark word;然後用CAS更新Lock Record的指針,更新成功直接執行,否則判斷是否是指向當前線程;如果是直接執行,否則自旋CAS更新Lock Record的指針
    自旋到達指定次數或者再有一個線程來競爭鎖就會升級到重量級鎖
    鎖釋放使用CAS將copy的mark word寫回,並改寫偏向標誌、標誌位

  • 重量級鎖

    對象頭內容:偏向標誌(0)、標誌位(10)、Monitor的指針;
    膨脹至重量級鎖時,會創建ObjectMonitor對象並copy mark word
    持有鎖的線程會阻塞其他線程,通過對象內部的監視器(monitor)實現,而其中 monitor 的本質是依賴於底層操作系統的 Mutex Lock 實現
    鎖釋放也是通過monitor對象,鎖釋放後會回寫mark word

  • 升級過程(懶得畫了,從網上找到一個圖)
    鎖升級過程

對象頭的操作

如上【對象頭結構】圖,對象頭分爲MarkWord、Class Metadata;Class Metadata存儲對象類型的指針(指向元空間的類對象),MarkWord不同狀態存儲不同內容,附上一張64位系統空間分配明細圖(借用)
mark word

  • 查看工具(org.openjdk.jol)

    maven配置
    <dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.8</version> </dependency>
    使用 ClassLayout.parseInstance(object).toPrintable() 可以獲取到對象頭內容,如下圖

  • 打印內容 對象頭內容

    紅框部分是存儲值,當前對象是數組,第4行是數組長度,非數組對象只有3行
    第3行是Class Metadata,jvm默認開啓指針壓縮,所以是32位,如果關閉則是64位;打印出來會多一行 3、4行
    第1、2行是MarkWord內容,位置使用排序是從第2行開始,每行按組從右向左,每8位一組,如下圖順序
    mark word
    第8組的第7、8位是標誌位,無鎖、偏向鎖下第6位是偏向標誌;
    第8組的第2~5位是分代年齡,第1位應該是cms使用(開啓指針壓縮時)
    無鎖狀態且對象生成了hashCode,第4組的2~8位加上5、6、7組共31位存儲hashCode
    偏向鎖狀態且已偏向某線程,第7組的1~6位加上1~6組共54位存儲線程ID,第7組的7、8位存儲epoch

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章