天天看點

synchronized鎖更新原理

Java對象的記憶體結構

對象記憶體結構

在64位作業系統下,

MarkWord(下圖_mark)占64位

KlassWord(下圖_klass)占32位   64位系統的Klass Word不是32位,預設64位,開啟指針壓縮後為32(感謝評論老哥的指出)

64位系統的Klass Word不是32位,預設64位,開啟指針壓縮後為32

_lengh(隻有資料對象才有,不考慮)

執行個體資料(下圖instance data)看參數的類型,int就占32位(4byte)

補齊(padding)是JVM規定java對象記憶體必須是8byte的倍數,如果執行個體資料占2byte,那麼(64bit的Markword+32bit的Klassword+執行個體資料32bit)=128bit=16byte是8byte的倍數,是以padding部分為0。

synchronized鎖更新原理

檢視對象記憶體結構

JDK8

<dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.9</version>
        </dependency>      
public class SynchronizedDemo {
  public static void main(String[] args) {
    Dog dog = new Dog();
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());
  }
}

class Dog {
  char age;
}      
synchronized鎖更新原理

如上圖所示,對象頭中的MarkWord占8byte,KlassWord占4個byte,執行個體屬性age是char類型占2個byte,那麼此時加起來為14byte,為了滿足是8的倍數,要補充2個byte。

下圖是當Dog對象裡的age變為int時列印的結果,請自行對比。

synchronized鎖更新原理

對象頭

下圖是引自《深入了解Java虛拟機:JVM進階特性與最佳實踐(第3版) 周志明》中的一個圖,下圖是32作業系統下的對象頭中的Mark Word(32位),Klass Word(32位),一共是64位。

64作業系統下,Mark Word的長度是64,在加Klass Word(32位),一共是96位,其實對象頭長什麼樣其實不是本文的重點,本文的重點是驗證鎖更新的過程,是以我們隻需要關注對象頭中Mark Word的最後3位即可,如下圖中的後3位。

synchronized鎖更新原理

鎖更新的過程

鎖狀态 25bit 4bit 1bit 2bit
23bit 2bit 是否偏向鎖 鎖标志位
1 無鎖 對象的HashCode 分代年齡 01
2 無鎖 對象的HashCode 分代年齡 1 01
3 偏向鎖 線程ID Epoch 分代年齡 1 01
4 輕量級鎖 指向棧中鎖記錄的指針 00
5 重量級鎖 指向重量級鎖的指針 10
6 GC标記 11

前提

由于大小端引起的問題,使得這裡展示的高低位相反,如下圖所示,是以我要關注的就是⑧位置的最後3位足矣。

synchronized鎖更新原理

無鎖狀态

public class SynchronizedDemo {
  public static void main(String[] args) {
    Dog dog = new Dog();
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());
  }
}      

如下圖所示,001表示的無鎖狀态并且不允許偏向 (其實預設是開啟偏向的,隻不過虛拟機後在運作後幾秒才開啟偏向鎖)

synchronized鎖更新原理

使用下面的參數,如下圖所示 ,會發現狀态為101,表示無鎖狀态

-XX:BiasedLockingStartupDelay=0      
synchronized鎖更新原理

由無鎖狀态---->偏向鎖狀态

單線程通路鎖的時候,鎖由無鎖狀态變為偏向鎖狀态。

// -XX:BiasedLockingStartupDelay=0
public class SynchronizedDemo {
  public static void main(String[] args) {
    Dog dog = new Dog();
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());
    //上鎖
    synchronized (dog){
      System.out.println(ClassLayout.parseInstance(dog).toPrintable());
    }
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());
  }
}

class Dog {
  int age;
}      
synchronized鎖更新原理

如上圖所示,開始狀态為101,為可偏向,無鎖狀态

上鎖後狀态是101,為可偏向,有鎖狀态 

解鎖後:狀态為101,為可偏向,有鎖狀态

差別為:當線程給無鎖狀态的lock加鎖時,會把線程ID存儲到MarkWord中,即鎖偏向于該ID的線程,偏向鎖不會自動釋放。

上面表格中2->3的過程。

偏向鎖狀态---->輕量級鎖狀态

多線程使用鎖(不競争,錯開時間通路),鎖由偏向鎖狀态變為輕量級鎖狀态

// -XX:BiasedLockingStartupDelay=0
public class SynchronizedDemo {
  public static void main(String[] args) {
    Dog dog = new Dog();
    System.out.println("初始狀态:");
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());

    new Thread(
            () -> {
              synchronized (dog) {
                System.out.println("hello world");
              }
            },
            "t1")
        .start();
    System.out.println("線程1釋放鎖後:");
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());
    try {
      TimeUnit.SECONDS.sleep(3);
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
    }
    new Thread(
            () -> {
              synchronized (dog) {
                System.out.println("線程2上鎖:");
                System.out.println(ClassLayout.parseInstance(dog).toPrintable());
              }
              System.out.println("線程2釋放鎖:");
              System.out.println(ClassLayout.parseInstance(dog).toPrintable());
            },
            "t2")
        .start();
  }
}

class Dog {
  int age;
}      
synchronized鎖更新原理

初始狀态為101,為可偏向,并且為無鎖狀态

線程1釋放鎖後,狀态為101,并且存儲了線程ID,為偏向鎖狀态,偏向于線程1

線程2上鎖,上鎖後,狀态為00,輕量級鎖狀态

線程2釋放鎖後,狀态為001,此時為不可偏向的無鎖狀态。

重量級鎖狀态

// -XX:BiasedLockingStartupDelay=0
public class SynchronizedDemo {
  public static void main(String[] args) {
    Dog dog = new Dog();
    System.out.println("初始狀态:");
    System.out.println(ClassLayout.parseInstance(dog).toPrintable());

    new Thread(
            () -> {
              synchronized (dog) {
                System.out.println("");
                try {
                  TimeUnit.SECONDS.sleep(3);
                } catch (Exception e) {
                  e.printStackTrace();
                } finally {
                }
              }
            },
            "t1")
        .start();

    new Thread(
            () -> {
              synchronized (dog) {
                System.out.println("線程2上鎖");
                System.out.println(ClassLayout.parseInstance(dog).toPrintable());
                try {
                  TimeUnit.SECONDS.sleep(3);
                } catch (Exception e) {
                  e.printStackTrace();
                } finally {
                }
              }
              System.out.println("線程2釋放鎖");
              System.out.println(ClassLayout.parseInstance(dog).toPrintable());
            },
            "t2")
        .start();
  }
}

class Dog {
  int age;
}      
synchronized鎖更新原理

如上圖所示,鎖初始狀态為101,可偏向無鎖狀态

當線程1在使用鎖,而線程2去上鎖的時候,狀态已經變為010,不可偏向重量級鎖。

總結

單線程使用鎖的時候為偏向鎖。

多線程無競争(錯峰使用鎖)的時候為輕量級鎖。

有競争的時候為重量級鎖。

參考