文章目錄
- 1. 概述
- 2. Mark Word
- 3. 非鎖不可偏向狀態與非鎖可偏向狀態的區別
- 4. 偏向鎖
- 5. 輕量級鎖
- 6. 重量級鎖
- 7. synchronized鎖升級總結
1. 概述
在jdk1.6之后,對synchronized進行了優化,對于一個鎖物件有如下6種狀態:
非鎖不可偏向狀態、非鎖可偏向狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態和GC狀態,
本篇博客將介紹6種狀態的區別以及synchronized的升級膨脹的一個程序,
2. Mark Word
在說明6種狀態的不同之前,先來了解一下什么是Mark Word,Mark Word中所記錄的資訊包含了鎖物件的6種狀態,
一個物件在java堆當中由3部分組成:物件頭,物件屬性,對齊填充,其中對齊填充不是必須有的,是為了保證整個物件的大小是8位元組的倍數,

物件頭又是由兩部分組成的,一部分是Mark Word,另一部分是一個指標指向方法區(元空間)中的類元資訊,也就是表明這個物件屬于哪個類,
Mark Word這里我們探討的是64位的情況
下面先來看看這6種狀態,在Mark Word當中是如何表示的:
-
非鎖不可偏向狀態

前25位不使用;后31位用于記錄物件的hashcode,但是僅有當呼叫物件的hashcode()方法的時候,才會去計算hashcode 并在mark word中有記錄,否則為0;后1位不使用;再后4位記錄物件的年齡;倒數第三位表示是否可偏向,這里是無鎖不可偏向狀態,所以這一位為0,最后兩位表示當前物件的狀態,此時是01,
所以最后三位是001的情況下 是無鎖不可偏向狀態,
-
非鎖可偏向狀態

和上面差不多,只是倒數第三位上不同,可偏向狀態,所以這一位是1,最后兩位和上面相同,
-
偏向鎖狀態

前54位記錄持有這個偏向鎖的執行緒id,后兩位記錄偏向的時間戳,剩余部分和上面的相同,所以根據mark word來看這個鎖是不是偏向鎖要看三個部分,第一是看倒數第三位,是否可偏向,是1的話表示可偏向,再看最后兩位 如果是01的話,此時可能是偏向鎖,再看最前面54位,如果記錄的是一個執行緒的id,那么這個物件就是一個偏向鎖,
-
輕量級鎖狀態

前62位記錄了某個執行緒堆疊中的鎖記錄的地址,后面再來說這個鎖記錄記錄了些什么資訊,最后兩位是00,表示輕量級鎖,
-
重量級鎖狀態

前62位記錄了堆中一個monitor物件的地址,最后兩位是10,表示重量級鎖,
-
GC狀態

最后兩位記錄的是11,表示該物件將在下一次垃圾回收的時候被處理掉,
3. 非鎖不可偏向狀態與非鎖可偏向狀態的區別
首先是非鎖,說明這兩種狀態下,當前物件還不是一個鎖,僅僅是一個普通的物件而已,
不同點就在于一個是不可偏向狀態,另一個是可偏向狀態,
可偏向狀態很好理解,就是當這個物件作為一個鎖,被一個執行緒所持有的時候,這個物件會變成一個偏向鎖的狀態,
那么為什么會有一個不可偏向狀態呢?
因為運行java程式啟動虛擬機的時候,JVM內部的代碼也有很多地方用到了synchronized,如果還需要從偏向狀態再逐步的升級,那么是沒有必要的,所以JVM設定了一個偏向鎖的啟動延遲,相關的JVM引數是-XX:BiasedLockingStartupDelay=4000,可以設定成0,表示取消啟動延遲,
啟動延遲表示在這個時間還沒到的時候,所創建出來的物件,就不讓他有機會從偏向鎖的狀態開始升級,而是直接從輕量級鎖的狀態開始,
下面是代碼證明:
//代碼1
@Slf4j(topic = "s")
public class TestSync {
static Object lock = new Object();
public static void main(String[] args){
Lock lock = new Lock();
ClassLayout classLayout = ClassLayout.parseInstance(lock);
log.debug(classLayout.toPrintable());
}
}
//代碼2
@Slf4j(topic = "s")
public class TestSync {
static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
Lock lock = new Lock();
ClassLayout classLayout = ClassLayout.parseInstance(lock);
log.debug(classLayout.toPrintable());
}
}
上面兩段代碼,區別在于,第二段代碼再物件創建之前先延遲了4秒鐘,輸出結果分別如下:
代碼1的結果:

代碼2的結果:

看到這個結果肯定會很疑惑,不是說前25位沒有使用嗎,其實這是因為作業系統大小端存盤的問題,本機作業系統采用的是小端存盤,是指一個資料的低位位元組序內容存放在低地址處,高位位元組序的內容存放在高地址處,不太明白的可以看看這篇博客《大小端(資料在記憶體中的存盤)》
從結果上可以看到,沒有加延遲的代碼,創建出來的物件是非鎖不可偏向狀態,而加了延遲4秒的代碼,創建出來的物件是非鎖可偏向狀態,
4. 偏向鎖
一個鎖物件,僅有一個執行緒在獲取它的時候,會使用cas來設定mark word持有這個鎖的執行緒的id,此時這個鎖就是偏向鎖,之后該執行緒在進入和退出同步塊的時候,不需要進行cas操作來加鎖和解鎖,只需要檢查一下這個物件頭中的mark word里是否存盤著當前執行緒的id即可,
代碼證明:
@Slf4j(topic = "s")
public class TestSync {
static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
Lock lock = new Lock();
ClassLayout classLayout = ClassLayout.parseInstance(lock);
log.debug(classLayout.toPrintable());
new Thread(()->{
synchronized (lock){
log.debug("------------偏向鎖 第一次------------");
ClassLayout c = ClassLayout.parseInstance(lock);
log.debug(c.toPrintable());
}
synchronized (lock){
log.debug("------------偏向鎖 第二次------------");
ClassLayout c = ClassLayout.parseInstance(lock);
log.debug(c.toPrintable());
}
synchronized (lock){
log.debug("------------偏向鎖 第三次------------");
ClassLayout c = ClassLayout.parseInstance(lock);
log.debug(c.toPrintable());
}
},"t1").start();
}
運行的結果如下:
關注輸出結果的當中劃橫線的三位就可以看出當前鎖的狀態
一開始末位3位101 表示 非鎖可偏向狀態,當執行緒t1第一次嘗試獲得鎖的時候,mark word中的前54位就記錄了 當前執行緒的id = 0x7fad098a,此時的鎖就成了偏向鎖,之后第二次第三次獲得鎖,可以看到 mark word中記錄的資訊沒變 此時鎖還是偏向鎖,

5. 輕量級鎖
當有多個執行緒去獲得鎖的時候,分兩種情況
第一種是多個執行緒先后交替的去獲得鎖,此時不存在同時搶鎖,物件鎖會從偏向鎖轉變成輕量級鎖,
當一個執行緒嘗試的去獲得物件鎖,會將當前物件鎖的mark word復制到執行緒堆疊中的鎖記錄當中,然后使用cas 去修改物件鎖的中的mark word的內容,設定一個指標指向執行緒堆疊中的鎖記錄,如果cas成功則獲得到這個鎖,
一個執行緒釋放鎖的時候,使用cas將鎖記錄當中的mark word 替換回物件頭當中,
第二種情況是多個執行緒存在同時搶鎖,假設現在有2個執行緒,第一個執行緒得到了鎖,第二個執行緒必定是拿不到鎖的,需要等到第一個執行緒釋放掉鎖,此時第二個執行緒并不會阻塞,而是采用自旋的方式繼續使用cas來嘗試的獲得鎖,若在自旋的次數還沒達到某個閾值之前,第二個執行緒獲得了鎖,那么此時的物件鎖還是輕量級的鎖,
代碼證明:
@Slf4j(topic = "s")
public class TestSync {
static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
Lock lock = new Lock();
ClassLayout classLayout = ClassLayout.parseInstance(lock);
log.debug(classLayout.toPrintable());
new Thread(()->{
synchronized (lock){
log.debug("------------偏向鎖 第一次------------");
ClassLayout c = ClassLayout.parseInstance(lock);
log.debug(c.toPrintable());
}
},"t1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
synchronized (lock){
log.debug("------------輕量級鎖------------");
ClassLayout c = ClassLayout.parseInstance(lock);
log.debug(c.toPrintable());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("鎖釋放了之后");
ClassLayout c = ClassLayout.parseInstance(lock);
log.debug(c.toPrintable());
},"t2").start();
}
}
運行的結果如下:
一開始是非鎖可偏向狀態,當t1執行緒獲得鎖的時候,鎖物件變成了偏向鎖的狀態
當t2執行緒也來拿鎖的時候,鎖物件升級成了輕量級鎖的狀態,
t2將鎖釋放了之后,鎖物件成了非鎖不可偏向狀態,說明之后再有執行緒獲得鎖,也是從輕量級鎖的狀態開始,

6. 重量級鎖
也是發生在多個執行緒拿鎖的時候,
在上面一節中第二種情況,第二個執行緒自旋的次數超過閾值的時候,此時的鎖就會變成重量級的鎖,重量級鎖中的指標的不再指向是執行緒堆疊中的鎖記錄,而是指向一個堆中的monitor物件,這個monitor物件會記錄當前持有鎖的執行緒,
代碼證明:
@Slf4j(topic = "s")
public class TestSync {
static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
Lock lock = new Lock();
ClassLayout classLayout = ClassLayout.parseInstance(lock);
log.debug(classLayout.toPrintable());
new Thread(()->{
synchronized (lock){
log.debug("------------偏向鎖 第一次------------");
ClassLayout c = ClassLayout.parseInstance(lock);
log.debug(c.toPrintable());
}
},"t1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
synchronized (lock){
log.debug("------------輕量級鎖------------");
ClassLayout c = ClassLayout.parseInstance(lock);
log.debug(c.toPrintable());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
synchronized (lock) {
log.debug("------------重量級鎖------------");
ClassLayout c2 = ClassLayout.parseInstance(lock);
log.debug(c2.toPrintable());
}
},"t3").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t2").start();
}
}
運行結果如下:
前面的就不說了,t2 執行緒獲得了鎖之后,在內部又創建了一個t3 執行緒,t3執行緒也要去拿鎖,此時t3執行緒拿不到鎖,會先進行自旋,超過了閾值之后,鎖就成了重量級的鎖了,t3會進入阻塞佇列,等到t2執行緒把鎖釋放掉,t3被喚醒再繼續執行,

7. synchronized鎖升級總結
- JVM 4秒默認的偏向鎖啟動延遲
- 若不考慮偏向鎖的啟動延遲,當僅有一個執行緒拿鎖的時候,鎖物件的狀態是偏向鎖狀態
- 當多個執行緒拿鎖的時候,若是交替拿鎖不發生競爭或者發生競爭執行緒自旋的次數小于某閾值,偏向鎖升級成輕量級鎖,
- 當自旋的次數超過某閾值,輕量級鎖升級成重量級鎖
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/245809.html
標籤:java
