java併發編程系列(二):synchronized的實現原理簡介

  synchronized可能Java多線程之間實現同步最常用的方式之一了,但是Java底層是如何實現的?僅在此記錄自己的理解

一:java編程中使用synchronized實現線程同步

  1. 對於普通方法,synchronized的鎖對象是當前對象(this)
  2. 對於類方法(static方法),synchronized的鎖對象是當前類的class對象
  3. 在同步代碼塊中的鎖是括號裏的對象

二:實現原理

synchronized實現同步的基礎: java中的每一個對象都可以作爲鎖,都有一個對應的監視器(monitor)當一個線程訪問到同步代碼塊要獲取鎖,退出代碼塊和發生異常要釋放鎖,那麼是如何實現的?
       同步方法和同步代碼塊實現方式都是基於進入和退出monitor對象實現的,具體細節不一樣.
       同步代碼塊方式通過在編譯後的代碼塊開始的地方插入monitorenter指令,在代碼塊結束的地方或者異常的地方插入monitorexit.當執行到monitorenter時嘗試獲取鎖,當執行到monitorexit時嘗試釋放鎖.
       同步方法是通過調用指令讀取運行時常量池中方法的 ACC_SYNCHRONIZED 標誌來隱式實現的

1.Java對象頭

Java中對象在內存中分爲三部分存儲:對象頭,實例變量,填充數據.關於這部分有篇博客寫的很好:https://blog.csdn.net/lkforce/article/details/81128115.
Java對象頭由三部分組成:
1,Mark Word

2,指向類的指針

3,數組長度(只有數組對象纔有)

對象頭的Mark Word中主要存儲鎖相關信息,對象頭結構:
在這裏插入圖片描述
Mark Word在默認情況下存儲着對象的HashCode、分代年齡、鎖標記位等以下是32位JVM的Mark Word默認存儲結構:
在這裏插入圖片描述
在運行期間,Mark word裏存儲的數據會隨着鎖標誌位的變化而變化,可能有以下四種:
在這裏插入圖片描述
每一個對象和一個monitor對象關聯(監視器),Mark word中的指針就是指向monitor對象
monitor對象中需要關注以下幾個數據
2個隊列:_WaitSet 和 _EntryList
持有鎖的線程id:_owner
當多個線程同時訪問一段同步代碼時,首先會進入 _EntryList 集合,當線程獲取到對象的monitor後, monitor中的owner變量設置爲當前線程同時monitor中的計數器count加1,若線程調用 wait() 方法,將釋放當前持有的monitor,owner變量恢復爲null,count自減1,同時該線程進入 WaitSe t集合中等待被喚醒。若當前線程執行完畢也將釋放monitor(鎖)並復位變量的值,以便其他線程進入獲取monitor(鎖)

2.鎖升級

偏向鎖:(無競爭)
hotspot的作者研究發現,大多數情況下鎖不存在多線程競爭,而且總是由同一線程多次獲得.所以爲例提高性能,引入了偏向鎖.當同一個線程多次獲取同一個鎖,第二次以後就不需要再通過同步來獲取鎖.但是如果競爭激烈,鎖會升級爲輕量級鎖

輕量級鎖(適用於多線程依次執行同步)
當有一定競爭,偏向鎖失敗,鎖結構變爲輕量級鎖.將對象頭的mark down複製到線程棧中,將mark down執行鎖(以上2個操作都是使用CAS),如果成功獲取鎖成功,反之失敗(膨脹爲重量級鎖)

重量級鎖
競爭太激烈的時候,會通過使用自旋方式不斷嘗試獲取鎖(CAS)

3:synchronized獲取鎖的原理

代碼塊:
當執行monitorenter指令時,當前線程將試圖獲取 objectref(即對象鎖) 所對應的 monitor 的持有權,當 objectref 的 monitor 的進入計數器爲 0,那線程可以成功取得 monitor,並將計數器值設置爲 1,取鎖成功。如果當前線程已經擁有 objectref 的 monitor 的持有權,那它可以重入這個 monitor (關於重入性稍後會分析),重入時計數器的值也會加 1。倘若其他線程已經擁有 objectref 的 monitor 的所有權,那當前線程將被阻塞(通過CAS等待獲取鎖),直到正在執行線程執行完畢,即monitorexit指令被執行,執行線程將釋放 monitor(鎖)並設置計數器值爲0 ,其他線程將有機會持有 monitor 。值得注意的是編譯器將會確保無論方法通過何種方式完成,方法中調用過的每條 monitorenter 指令都有執行其對應 monitorexit 指令,而無論這個方法是正常結束還是異常結束。爲了保證在方法異常完成時 monitorenter 和 monitorexit 指令依然可以正確配對執行,編譯器會自動產生一個異常處理器,這個異常處理器聲明可處理所有的異常,它的目的就是用來執行 monitorexit 指令

方法:
JVM可以從方法常量池中的方法表結構(method_info Structure) 中的 ACC_SYNCHRONIZED 訪問標誌區分一個方法是否同步方法。當方法調用時,調用指令將會 檢查方法的 ACC_SYNCHRONIZED 訪問標誌是否被設置,如果設置了,執行線程將先持有monitor(虛擬機規範中用的是管程一詞), 然後再執行方法,最後再方法完成(無論是正常完成還是非正常完成)時釋放monitor。在方法執行期間,執行線程持有了monitor,其他任何線程都無法再獲得同一個monitor。如果一個同步方法執行期間拋 出了異常,並且在方法內部無法處理此異常,那這個同步方法所持有的monitor將在異常拋到同步方法之外時自動釋放

三:參考

https://www.cnblogs.com/paddix/p/5367116.html
https://blog.csdn.net/javazejian/article/details/72828483

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