淺談Java併發編程系列(七) —— 深刻解析synchronized關鍵字

Synchronized關鍵字

synchronized的鎖機制的主要優點是Java語言內置的鎖機制,所以,JVM能夠自由的優化而不影響已存在的代碼。java

任何對象都擁有對象頭這一數據結構來支持鎖,可是對於較大的對象系統開銷會更大一些。安全

java中的每個對象都至少包含2個字(24 Bytes for 32bits & 28 Bytes for 64bits, 不包括已壓縮的對象)。第一個字被稱爲Mark Word。這是一個對象的頭,它包含了不一樣的信息,包括鎖的相關信息。
第二個字是指向metadata class的指針,metadata class字義了對象的類型。這部分也包含了VMT(Virtual Method Table)。數據結構

Mark Word 的結構以下所示:優化

mark word

Mark Word根據最低兩位(Tag)的所表示的狀態,編碼了不一樣的信息。
若是這個對象沒有被用做鎖,Mark Word 記錄了hashcode和對象年齡(for GC/survivors)。
除此以外,有3種狀態對應鎖:輕量級鎖,重量級鎖和偏向鎖。編碼

經量級鎖

全部現代JVM都引入了經量級鎖:spa

  • 避免將每一個對象關聯操做系統的mutex/condition變量(重量級鎖)操作系統

  • 當不存在鎖競爭時,使用原子操做來進入退出同步塊線程

  • 若是發生鎖競爭,回退到操做系統的重量級鎖指針

引入輕量級鎖會提供鎖效率,由於大部分鎖都不存在競爭。code

經量級鎖的加鎖過程:

  • 當一個對象被鎖定時,mark word被複制到當前嘗試獲取鎖的線程的線程棧(Execution stack)的鎖記錄空間(lock record), 被複制的mark word官方稱爲displaced mark。

  • 使用CAS操做來嘗試使 mark word指向當前線程的鎖記錄空間(即在mark word中存入使用當前線程鎖記錄空間的指針——stack pointer)。

  • 若是CAS操做成功,則線程得到鎖。

  • 若是CAS操做失敗,即代表存在鎖競爭,則發生鎖膨脹,回退到重量級鎖。

  • 鎖記錄空間中記錄了被當前執行方法鎖定的對象(經過遍歷線程棧找到線程的鎖對象)

經量級鎖加鎖前:
Light-weight Locking : Before

經量級鎖加鎖後:
Ligth-weight Locking : After

經量級鎖的解鎖過程:

  • 解鎖使用CAS來把displaced mark寫回對象的mark word中。

  • 若是CAS失敗, 表示發生鎖競爭:則鎖膨脹。(通知其餘等待線程鎖已釋放)

  • 將鎖記錄空間置爲0:若是發生鎖膨脹,則用0替換displaced mardk,若是不存在競爭,則CAS將鎖記錄空間置爲0後,中止CAS操做。

偏向鎖

偏向鎖的引入:

  • 在多處理器上CAS操做可能開銷很大。

  • 大多數鎖不只不存在競爭,並且每每由同一個線程使用。

  • 使單獨一個線程獲取鎖的開銷更低。

  • 代價是使另外一個線程獲取鎖開銷增大。

偏向鎖加鎖過程:

當鎖對象第一次被線程獲取時,VM把對象頭中的標誌位設爲101,即偏向模式。同時使用CAS把獲取到這個鎖的線程ID記錄在對象的mark word中,若是CAS成功,則持有偏向鎖的線程之後每次進行這個鎖相關的同步塊時,再也不進行任務同步操做,只進行比較Mark word中的線程ID是不是當前線程的ID。

偏向鎖的解鎖過程:

當另一個線程去嘗試獲取這個鎖時,偏向模式結束。根據鎖對象目前是否處於被鎖定狀態,撤銷偏向後恢復到未鎖定或經量級鎖定狀態。

VM會中止持有偏向鎖的線程(實際上,VM不能中止單一線程,而是在安全點進行的操做)。
遍歷持有偏向鎖的線程的棧,找到鎖記錄空間,將displaced mark 寫入到最舊的鎖記錄空間,其餘的寫0。
更新鎖對象的mark word。若是被鎖定,則指向最舊的鎖記錄空間,不然,填入未鎖定值。

偏向鎖的特色:

  • 偏向於第一個獲取鎖的線程:

    • 在mark word的Tag中增長一位

    • 001表示無鎖狀態

    • 101表示偏向或可偏向狀態(thread ID ==0 == unlock)

    • 經過CAS來獲取偏向鎖

  • 對於持有鎖的線程接下的鎖獲取和釋放開銷很是小(僅僅判斷下,不須要CAS同步操做)。

  • 若是另外一個線程鎖定了偏向鎖對象,則偏向鎖收回,升級爲輕量級鎖(增長了另外一個線程獲取鎖的開銷)。

相關文章
相關標籤/搜索