java併發源碼:Synchronized

java併發源碼:Synchronized

synchronized加鎖方式:

  • 修飾實例方法:鎖是當前對象。
  • 修飾靜態方法:鎖是當前類的class對象。
  • 修飾代碼塊:鎖是synchronized括號裏配置的對象。

​ JVM基於進入和退出Monitor對象來實現方法同步和代碼塊同步的。代碼塊同步是用monitorenter 和monitorexit指令實現的。

 public void test() {
   synchronized (this) {
   x++;
   }
 }
Code:
   0: aload_0
   1: dup
   2: astore_1
   3: monitorenter
   4: aload_0
   5: dup
   6: getfield      #2                  // Field x:I
   9: iconst_1
  10: iadd
  11: putfield      #2                  // Field x:I
  14: aload_1
  15: monitorexit
  16: goto          24
  19: astore_2
  20: aload_1
  21: monitorexit
  22: aload_2
  23: athrow
  24: return
Exception table:
   from    to  target type
       4    16    19   any
      19    22    19   any

可以看到3的monitorenter指令和15的monitorexit指令

synchronized用的鎖是存在java對象頭裏的。32位虛擬機java對象頭的存儲結構:

鎖狀態 25bit 4bit 1bit是否偏向鎖 2bit鎖標誌位
無鎖狀態 對象的hashcode 對象分代年齡 0 01

在運行期間,MarkWord數據會發生動態變化

鎖狀態 25bit 4bit 1bit 2bit
23bit 2bit 是否是偏向鎖 鎖標誌位
輕量級鎖 00
重量級鎖 10
GC標記
線程ID Epoch 對象分代年齡 1 01

在64位虛擬機下,MarkWord存儲結構:

鎖狀態 25bit 31bit 1bit 4bit 1bit 2bit
cms_free 分代年齡 偏向鎖 鎖標誌位
無鎖 unused hashCode 0 01
偏向鎖 1 01

synchronized優化

jdk1.6爲了減少獲得鎖和釋放鎖帶來的性能消耗,對synchronized獲取鎖進行了優化。

無鎖->偏向鎖->輕量級鎖->重量級鎖。

1、偏向鎖

​ 大多數情況下,鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,爲了讓線程獲得鎖的代價更低而引入了偏向鎖。當一個線程訪問同步塊並獲取鎖時,會在對象頭的鎖記錄裏存儲鎖偏向的線程ID,以後該線程在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需簡單地測試一下對象頭的MarkWord裏是否存儲着指向當前線程的偏向鎖。如果測試成功,表示線程已經獲得了鎖。如果測試失敗,則需要再測試一下MarkWord中偏向鎖的標識是否設置成1(表示當前是偏向鎖):如果沒有設置,則使用CAS競爭鎖;如果設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。

偏向鎖

2、輕量級鎖

輕量級鎖加鎖:

​ 線程在執行同步塊之前,JVM會現在當前線程的棧幀中創建用於存儲鎖記錄的空間,並將對象頭中的MarkWord複製到鎖記錄中,官方稱爲Displaced Mark Word。然後線程嘗試使用CAS將對象頭中的MarkWord替換爲指向鎖記錄的指針。如果成功,當前線程獲得鎖。如果失敗,表示有其它線程競爭,當前線程便嘗試使用自旋來獲取鎖。

輕量級鎖解鎖:

​ 輕量級解鎖時,會使用原子的CAS操作將Displaced Mark Word 替換回對象頭,如果成功,則表示沒有競爭發生。如果失敗,表示當前鎖存在競爭,鎖就會膨脹爲重量級鎖。

圖爲兩個線程同時爭奪鎖,導致鎖膨脹。

輕量級鎖

3、重量鎖

​ 由於自旋會消耗CPU,爲了避免無用的自旋,一旦鎖升級爲重量級鎖,就不會再恢復到輕量級鎖狀態。當鎖處於這個狀態,其它線程試圖獲取鎖,都會被阻塞住。

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