1. 程式人生 > >(2.1.27.6)Java併發程式設計:synchronized

(2.1.27.6)Java併發程式設計:synchronized

在Java中,最基本的互斥同步手段就是synchronized關鍵字。。Java併發程式設計這個領域中synchronized關鍵字一直都是元老級的角色,很久之前很多人都會稱它為 “重量級鎖” 。

Java的執行緒是對映到作業系統的原生執行緒之上的,如果要阻塞或喚醒一個執行緒,都需要作業系統來幫忙完成,這就需要從使用者態轉換到核心態中,因此狀態轉換需要耗費很多的處理器時間。 對於程式碼簡單的同步塊(如被synchronized修飾的getter()或setter()方法),狀態轉換消耗的時間有可能比使用者程式碼執行的時間還要長。所以synchronized是Java語言中一個重量級(Heavyweight)的操作. 有經驗的程式設計師都會在確實必要的情況下才使用這種操作。而虛擬機器本身也會進行一些優化,譬如在通知作業系統阻塞執行緒之前加入一段自旋等待過程,避免頻繁地切入到核心態之中。

但是,在JavaSE 1.6之後進行了主要包括為了減少獲得鎖和釋放鎖帶來的效能消耗而引入的 偏向鎖 和 輕量級鎖 以及其它各種優化之後變得在某些情況下並不是那麼重了。

synchronized的底層實現主要依靠 Lock-Free 的佇列,基本思路是 自旋後阻塞,競爭切換後繼續競爭鎖,稍微犧牲了公平性,但獲得了高吞吐量。線上程衝突較少的情況下,可以獲得和CAS類似的效能;而執行緒衝突嚴重的情況下,效能遠高於CAS。

一、synchronized的三種使用方式

瞭解了synchronized的解決的問題,那麼我們繼續來看看在Java中在Java中synchronized的使用情況。

在Java中synchronized主要有三種使用的情況。下面分別列出了這幾種情況

  • 修飾普通的例項方法,對於普通的同步方法,鎖式當前例項物件
  • 修飾靜態方法,對於靜態同步方法,鎖式當前類的Class物件
  • 修飾程式碼塊,對於同步方法塊,鎖是Synchronized配置的物件

1.1 證明當前普通的同步方法,鎖式當前例項物件

lass SynchronizedDemo {

    public synchronized void normalMethod() {// 證明這裡,鎖的是當前示例物件
        doPrint(5);
    }
 
    public void blockMethod() {//注意,同步塊方法塊中,配置的是當前類的物件
        synchronized (this) {
            doPrint(5);
        }
    }
	//列印當前執行緒資訊與角標值
    private static void doPrint(int index) {
        while (index-- > 0) {
            System.out.println(Thread.currentThread().getName() + "--->" + index);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        SynchronizedDemo demo = new SynchronizedDemo();
        new Thread(() -> demo.normalMethod(), "testNormalMethod").start();
        new Thread(() -> demo.normalMethod(), "testBlockMethod").start();
    }
 }

分別建立了兩個方法,normalMethod()與blockMethod()方法

  • 其中normalMethod()方法為普通的同步方法
  • blockMethod()方法中,是一個同步塊且配置的物件是當前類的物件。

在Main()方法中,分別建立兩個執行緒執行兩個不同的方法。

在這裡插入圖片描述 【證明當前普通的同步方法,鎖式當前例項物件】】

觀察程式輸出結果,我們可以看到

  1. normalMethod方法是由於blockMethod方法執行的
  2. 且blockMethod方法是在normalMethod方法執行完成之後在執行的。

也就證明了我們的對於普通的同步方法鎖式當前例項物件的結論。

1.2 證明對於靜態同步方法,鎖式當前類的Class物件

class SynchronizedDemo {

    public static synchronized void staticMethod() {// 證明這裡,鎖的是當前示例物件的類
        doPrint(5);
    }
	
    public void blockMethod() {
        synchronized (SynchronizedDemo.class) {//注意,同步塊方法塊中,配置的是當前類的Class物件
            doPrint(5);
        }
    }

    /**
     * 列印當前執行緒資訊
     */
    private static void doPrint(int index) {
        while (index-- > 0) {
            System.out.println(Thread.currentThread().getName() + "--->" + index);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        SynchronizedDemo demo = new SynchronizedDemo();
        new Thread(() -> demo.blockMethod(), "testBlockMethod").start();
        new Thread(() -> demo.staticMethod(), "testStaticMethod").start();
    }

}

在這裡插入圖片描述 【證明對於靜態同步方法,鎖式當前類的Class物件】

觀察結果,也很明顯的證明了對於靜態同步方法,鎖式當前類的Class物件的結論

二、Synchronized結合Java Object物件中的wait,notify,notifyAll

在上文的例子中,我們其實都是展示了一個隱式同步的機制:被阻塞等待的執行緒在何時When被喚醒,並不是顯式的被使用者控制,而是必須等待持有鎖的執行緒完成程式碼區塊後自動觸發。 那麼如何做到顯式控制呢,我們就需要藉助一個訊號機制

在這裡我們先簡單的介紹下Java Object物件中的wait,notify,notifyAll。它們是定義在Object類的例項方法,用於控制執行緒狀態

三個方法都必須在synchronized 同步關鍵字所限定的作用域中呼叫,否則會報錯java.lang.IllegalMonitorStateException ,意思是因為沒有同步,所以執行緒對物件鎖的狀態是不確定的,不能呼叫這些方法。

  • wait
    • 表示持有物件鎖的執行緒A準備釋放物件鎖許可權,釋放cpu資源並進入等待。
  • notify
    • 表示持有物件鎖的執行緒A準備釋放物件鎖許可權,通知jvm喚醒某個競爭該物件鎖的執行緒X。
    • 執行緒A synchronized 程式碼作用域結束後,執行緒X直接獲得物件鎖許可權,其他競爭執行緒繼續等待(即使執行緒X同步完畢,釋放物件鎖,其他競爭執行緒仍然等待,直至有新的notify ,notifyAll被呼叫)。
  • notifyAll
    • 表示持有物件鎖的執行緒A準備釋放物件鎖許可權,通知jvm喚醒所有競爭該物件鎖的執行緒
    • 執行緒A synchronized 程式碼作用域結束後,jvm通過演算法將物件鎖許可權指派給某個執行緒X,所有被喚醒的執行緒不再等待。執行緒X synchronized 程式碼作用域結束後,之前所有被喚醒的執行緒都有可能獲得該物件鎖許可權,這個由JVM演算法決定
public class WaitNotifyCase {
    public static void main(String[] args) {
        final Object lock = new Object();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread A is waiting to get lock");
                synchronized (lock) {
                    try {
                        System.out.println("thread A get lock");
                        TimeUnit.SECONDS.sleep(1);
                        System.out.println("thread A do wait method");
                        lock.wait();
                        System.out.println("wait end");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread B is waiting to get lock");
                synchronized (lock) {
                    System.out.println("thread B get lock");
                    try {
                        TimeUnit.SECONDS.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lock.notify();
                    System.out.println("thread B do notify method");
                }
            }
        }).start();
    }
}

//執行結果:
thread A is waiting to get lock
thread A get lock
thread B is waiting to get lock
thread A do wait method
thread B get lock
thread B do notify method
wait end


三、Synchronized底層實現的前備知識

在瞭解Synchronized的原理的原理之前,我們需要知道三個知識點

  1. CAS操作
  2. Java物件頭(其中Synchronized使用的鎖就在物件頭中)
  3. jdk1.6對鎖的優化。

在瞭解以上三個知識點後,再去理解其原理就相對輕鬆一點。

3.1 Java物件的記憶體佈局與物件頭

在Java虛擬機器中,物件在記憶體的儲存的佈局可以分為3塊區域:

  1. 物件頭(Header)
    1. “Mark Word”
    2. 型別指標
    3. 記錄陣列長度的資料(可選)
  2. 例項資料(Instance Data)
  3. 對齊填充(Padding)

在這裡插入圖片描述 【Java物件的記憶體佈局】

Java物件頭的組成,主要分為以下3個:

  • “Mark Word“:
    • 用於儲存物件自身的執行時資料。如雜湊碼(HashCode)、GC分代年齡、鎖狀態標誌、執行緒持有的鎖、偏向鎖ID、偏向鎖時間戳等,這部分的資料在長度32位與64位的虛擬機器中分別為32bit和64bit,官方稱為“Mark Word"。
  • 型別指標
    • 物件頭的另一部分是型別指標,即物件指向它的類元資料的指標
    • 虛擬機器通過這個指標來確定這個物件是哪個類的例項。(Java SE 1.6中為了減少獲得鎖和釋放鎖帶來的效能消耗而引入的偏向鎖和輕量級鎖)
  • 記錄陣列長度資料:
    • 物件頭剩下的一部分是用於記錄陣列長度的資料(如果當前物件不是陣列,就沒有這一部分資料)
    • 如果物件是一個Java陣列,那在物件頭中還必須有一塊用於記錄陣列長度的資料。因為虛擬機器可以通過普通Java物件的元資料資訊來確定Java物件的大小,但是從陣列中的元資料中無法確定陣列的大小。

3.1.1 “Mark Word“資料結構

關於"Mark Word",因為儲存物件頭資訊是與物件身定義的資料無關的額外的儲存成本,考慮到虛擬機器的空間效率,"Mark Word"被設計成一個被設計成一個非固定的資料結構以便在極小的空間儲存儘量多的資訊,它會根據物件的狀態複用自己的儲存區域。

在JVM中,“Mark Word"的實現是在markOop.hpp檔案中的markOopDesc類。通過註釋我們大致瞭解”Mark Word"的結構,具體程式碼如下:

hash:儲存物件的雜湊碼
age:GC分代年齡
biased_lock:偏向鎖標誌
lock:鎖狀態標誌
JavaThread*  當前執行緒
epoch:儲存偏向時間戳

//  32 bits:
//  --------
//             hash:25 ------------>| age:4    biased_lock:1 lock:2 (normal object)
//             JavaThread*:23 epoch:2 age:4    biased_lock:1 lock:2 (biased object)
// 省略部分程式碼


//  64 bits:
//  --------
//  unused:25 hash:31 -->| unused:1   age:4    biased_lock:1 lock:2 (normal object)
//  JavaThread*:54 epoch:2 unused:1   age:4    biased_lock:1 lock:2 (biased object)
// 省略部分程式碼


//其中關於當前鎖的狀態標誌markOopDesc類中也進行了詳細的說明,具體程式碼如下:
  enum { locked_value             = 0,//輕量級鎖 對應[00] 
         unlocked_value           = 1,//無鎖狀態  對應[01]
         monitor_value            = 2,//重量級鎖 對應[10]
         marked_value             = 3,//GC標記  對應[11]
         biased_lock_pattern      = 5//是否是偏向鎖  對應[101] 其中biased_lock一個bit位,lock兩個bit位
  };

32位與64位2種不同位數的作業系統下的結構稍有不同,我們以32位的講解

  • 在無鎖狀態下,32位JVM的“Mark Word"的預設儲存結構
    • 25bit用於儲存物件雜湊碼
    • 4bit用於儲存物件分代年齡
    • 2bit用於儲存鎖標誌**(其中01標識當前執行緒為無鎖狀態)**,1bit固定為0。

在這裡插入圖片描述 【無鎖狀態】

  • 在有鎖狀態態下,32位JVM的“Mark Word"的預設儲存結構
    • 23個bit位用於儲存當前執行緒id
    • 2個bit位用於儲存偏向鎖時間戳
    • 4個bit為用於儲存分代年齡(用於GC)
    • 1個bit位儲存當前是否是偏向鎖
    • 最後的2bit用於當前鎖的不同狀態。其中00標識當前鎖為輕量級鎖,10標識為重量級鎖,01標識當前鎖為偏向鎖。

在這裡插入圖片描述 【有鎖狀態】

3.2 synchronized鎖優化

Java SE 1.6為了減少獲得鎖和釋放鎖帶來的效能消耗,引入了“偏向鎖”和“輕量級鎖”。在Java SE 1.6中,鎖一共有4種狀態,級別從低到高依次是:

  1. 無鎖狀態
  2. 偏向鎖狀態
  3. 輕量級鎖狀態
  4. 重量級鎖狀態

這幾個狀態會隨著競爭情況逐漸升級,鎖可以升級但不能降級,意味著偏向鎖升級成輕量級鎖後不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率

下面會對各種鎖進行介紹:

  • 偏向鎖
    • 在大多數情況下,鎖不僅不存在多執行緒競爭,而且總是由同一執行緒多次獲得。為了讓執行緒獲得鎖的代價更低而引入了偏向鎖
    • 當一個執行緒訪問同步塊並獲取鎖時,會在物件頭中的“Mark word"和棧幀中的鎖記錄裡儲存鎖偏向的執行緒ID。以後該執行緒在進入和退出同步塊時,不需要進行CAS操作來加鎖和解鎖。只需簡單地測試一下物件頭的”Mark Word“裡是否儲存著指向當前執行緒的偏向鎖。如果測試成功,表示執行緒已經獲得了鎖。如果測試失敗,則需要再測試一下“Mark Word”中偏向鎖的標識是否設定成1(表示當前是偏向鎖):如果沒有設定,則使用CAS競爭鎖;如果設定了,則嘗試使用CAS將物件頭的偏向鎖指向當前執行緒。
  • 輕量級鎖
    • 執行緒在執行同步塊之前,JVM會先在當前執行緒的棧楨中建立用於儲存鎖記錄的空間,並將物件頭中的Mark Word複製到鎖記錄中,官方稱為Displaced Mark Word。
    • 然後執行緒嘗試使用CAS將物件頭中的Mark Word替換為指向鎖記錄的指標。如果成功,當前執行緒獲得鎖,如果失敗,表示其他執行緒競爭鎖,當前執行緒便嘗試使用自旋來獲取鎖。
  • 重量級鎖
    • 輕量級解鎖時,會使用原子的CAS操作將Displaced Mark Word替換回到物件頭,成功則表示沒有競爭發生。如果失敗,表示當前鎖存在競爭,鎖就會膨脹成重量級鎖,重量級鎖會導致競爭的執行緒互斥同步。

四、Synchronized的底層程式碼實現

JVM基於進入和退出Monitor物件來實現方法同步和程式碼塊同步,但兩者的實現細節不一樣。程式碼塊同步是使用monitorenter和monitorexit指令實現的,而方法同步是使用位元組碼同步指令ACC_SYNCHRONIZED來實現的,細節在JVM規範裡並沒有詳細說明。但是方法的同步同樣可以使用這兩個指令來實現。

位元組碼同步指令ACC_SYNCHRONIZED原理:JVM通過使用管程(Monitor)來支援同步,JVM可以從方法常量池的方法表結構中的ACC_SYNCHRONIZED訪問標誌來得知一個方法是否宣告為同步方法,當方法呼叫時,呼叫指令將會檢查方法的ACC_SYNCHRONIZED訪問標誌是否被設定,如果設定了,執行執行緒就要求先成功持有管程(Monitor),然後才能執行方法,最後當方法完成(無論是正常完成還是非正常完成)時釋放管程,在方法執行期間,執行執行緒持有了管程,其他任何執行緒都無法在獲取到同一個管程。

那我們這裡我們就以synchronized程式碼塊底層原理來進行講解。

public class SyncCodeBlock {
   public int i;
   public void syncTask(){
       //同步程式碼庫
       synchronized (this){
           i++;
       }
   }
}

然後我們通過javap指令反編譯得到位元組碼。

//===========主要看看syncTask方法實現================
  public void syncTask();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter  //注意此處,進入同步方法
         4: aload_0
         5: dup
         6: getfield      #2             // Field i:I
         9: iconst_1
        10: iadd
        11: putfield      #2            // Field i: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:
      //省略其他位元組碼.......
}

從上訴程式碼中,synchronized關鍵字經過編譯之後,會在同步塊的前後分別形成monitorenter和monitorexit這兩個位元組碼指令。當我們的JVM把位元組碼載入到記憶體的時候,會對這兩個指令進行解析。

這兩個位元組碼都需要一個reference型別的引數來指明要鎖定和解鎖的物件。如果Java程式中的synchronized明確指定了物件引數,那就是這個物件的reference;如果沒有明確指定,那就根據synchronized修飾的是例項方法還是類方法,去取對應的物件例項或Class物件來作為鎖物件。

其中關於monitorenter 與monitorenter的指令解析是通過InterpreterRuntime.cpp檔案中的InterpreterRuntime::monitorenter與InterpreterRuntime::monitorexit兩個函式分別實現的。

  • InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem)
  • InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem)

BasicObjectLock 持有BasicLock物件,BasicLock類中存在指向"Mark Word“的指標。那麼BasicObjectLock 就能訪問”Mark Word“中的內容了

在讀下文之前,要先明白一個概念:所謂的鎖,本質上就是一個共享變數,這個變數標識了某個共享物件是否被其他執行緒佔用。 執行緒在訪問共享物件時,要先判斷該共享變數。

4.1 InterpreterRuntime::monitorenter方法

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem)){
  //省略部分程式碼
  if (UseBiasedLocking) {//判斷是否使用偏向鎖
	//如果是使用偏向鎖,則進入偏向鎖獲取,避免不必要的膨脹。
    ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
  } else {//否則直接走輕量級鎖的獲取
    ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
  }
  //省略部分程式碼
}

monitorenter方法執行時,會先判斷當前是否開啟偏向鎖,如果沒有開啟會直接走輕量級鎖的獲取,也就是slow_enter()方法。

偏向鎖在Java 6和Java 7裡是預設啟用的,但是它在應用程式啟動幾秒鐘之後才啟用,如有必要可以使用JVM引數來關閉延遲:-XX:BiasedLockingStartupDelay=0。 如果你確定應用程式裡所有的鎖通常情況下處於競爭狀態,可以通過JVM引數關閉偏向鎖:-XX:-UseBiasedLocking=false,那麼程式預設會進入輕量級鎖狀態

4.1.1 偏向鎖的獲取

ObjectSynchronizer::fast_enter()方法是在sychronizer.cpp檔案進行宣告的,具體程式碼如下:



void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock,
                                    bool attempt_rebias, TRAPS) {
  if (UseBiasedLocking) {//如果使用偏向鎖
    if (!SafepointSynchronize::is_at_safepoint()) {//如果不在安全點,獲取當前偏向鎖的狀態(可能撤銷與重偏向)
      BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
      if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {//如果是撤銷與重偏向直接返回
        return;
      }
    } else {//如果在安全點,有可能會撤銷偏向鎖
      assert(!attempt_rebias, "can not rebias toward VM thread");
      BiasedLocking::revoke_at_safepoint(obj);
    }
   //省略部分程式碼
  }
  slow_enter(obj, lock, THREAD);//如果不使用偏向鎖,則走輕量級鎖的獲取
}

在該方法中如果當前JVM支援偏向鎖,會需要等待全域性安全點(在這個時間點上沒有正在執行的位元組碼)

  1. 如果當前不在安全點中,會呼叫revoke_and_rebias()方法來獲取當前偏向鎖的狀態(可能為撤銷或撤銷後重偏向)。
  2. 如果在安全點,會根據當前偏向鎖的狀態來判斷是否需要撤銷偏向鎖

4.1.1.1 BiasedLocking::revoke_and_rebias()方法

其中revoke_and_rebias()方法是在biasedLocking.cpp中進行宣告的。

BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) {
  assert(!SafepointSynchronize::is_at_safepoint(), "must not be called while at safepoint");
  markOop mark = obj->mark();
  
  //第一步,如果沒有其他執行緒佔用該物件(mark word中執行緒id為0,後三位為101,且不嘗試重偏向)
  //這裡“fast enter()方法"傳入的attempt_rebias為true
  if (mark->is_biased_anonymously() && !attempt_rebias) {
    //一般來講,只有在重新計算物件hashCode的時候才會進入該分支,
    //所以直接用用CAS操作將物件設定為無鎖狀態
    markOop biased_value       = mark;
    markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());
    markOop res_mark = obj->cas_set_mark(unbiased_prototype, mark);//cas 操作從新設定偏向鎖的狀態
    if (res_mark == biased_value) {//如果CAS操作失敗,說明存在競爭,偏向鎖為撤銷狀態
      return BIAS_REVOKED;
    }
  } else if (mark->has_bias_pattern()) {
    //第二步,判斷當前偏向鎖是否已經鎖定(不管mark word中執行緒id是否為null),嘗試重偏向
    Klass* k = obj->klass();
    markOop prototype_header = k->prototype_header();
    if (!prototype_header->has_bias_pattern()) {
     //第三步如果有執行緒對該物件進行了全域性鎖定(即同步了靜態方法/屬性),則取消偏向操作
      markOop biased_value       = mark;
      markOop res_mark = obj->cas_set_mark(prototype_header, mark);
      assert(!obj->mark()->has_bias_pattern(), "even if we raced, should still be revoked");
      return BIAS_REVOKED;//偏向鎖為撤銷狀態
    } else if (prototype_header->bias_epoch() != mark->bias_epoch()) {  //第四步,如果偏向鎖時間過期,(這個時候有另一個執行緒通過偏向鎖獲取到了這個物件的鎖)
      if (attempt_rebias) {//第五步,如果偏向鎖開啟,重新通過cas操作更新時間戳與分代年齡。
        assert(THREAD->is_Java_thread(), "");
        markOop biased_value       = mark;
        markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch());
        markOop res_mark = obj->cas_set_mark(rebiased_prototype, mark);
        if (res_mark == biased_value) {
          return BIAS_REVOKED_AND_REBIASED;//撤銷偏移後重偏向。
        }
      } else {//第六步,如果偏向鎖關閉,通過CAS操作更新分代年齡
        markOop biased_value       = mark;
        markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age());
        markOop res_mark = obj->cas_set_mark(unbiased_prototype, mark);
        if (res_mark == biased_value) {
          return BIAS_REVOKED;////如果CAS操作失敗,說明存在競爭,偏向鎖為撤銷狀態
        }
      }
    }
  }
 //省略部分程式碼...
}

偏向鎖的獲取由BiasedLocking::revoke_and_rebias方法實現,主要分為五個步驟

  1. 第一步,判斷當前偏向鎖中"Mark word"中執行緒id是否為null,且attempt_rebias =false。如果滿足條件,嘗試通過CAS操作將當前物件設定為無鎖狀態。如果CAS操作失敗,說明存在競爭,偏向鎖為撤銷狀態。
  2. 第二步,判斷當前偏向鎖是否已經鎖定(不管mark word中執行緒id是否為null),會根據當前條件走第三、第四、第五步。
  3. 第三步,如果有執行緒對該物件進行了全域性鎖定(即同步了靜態方法/屬性),偏向鎖為撤銷狀態。
  4. 第四步,判斷偏向鎖時間是否過期(這個時候有另一個執行緒通過偏向鎖獲取到了這個物件的鎖),接著走第五步、第六步的條件判斷
  5. 第五步,在偏向鎖時間過期的條件下,如果偏向鎖開啟,那麼通過CAS操作更新時間戳與分代年齡、執行緒ID,如果失敗,表明該物件的鎖狀態已經從撤銷偏向到了另一執行緒。當前偏向鎖的狀態為撤銷後重偏向。
  6. 第六步,在偏向鎖時間過期的條件下,如果偏向鎖預設關閉,那麼通過CAS操作更新分代年齡,如果失敗,說明存線上程的競爭,偏向鎖為撤銷狀態。

4.1.1.2 revoke_at_safepoint()

在上文中我們提到了在呼叫fast_enter()方法時,如果在安全點,這時會根據偏向鎖的狀態來判斷是否需要撤銷偏向鎖,也就是呼叫revoke_at_safepoint()方法。其中該方法也是在biasedLocking.cpp中進行宣告的,具體程式碼如下:

void BiasedLocking::revoke_at_safepoint(Handle h_obj) {
  assert(SafepointSynchronize::is_at_safepoint(), "must only be called while at safepoint");
  oop obj = h_obj();
  HeuristicsResult heuristics = update_heuristics(obj, false);//獲得偏向鎖偏向與撤銷的次數
  if (heuristics == HR_SINGLE_REVOKE) {//如果是一次撤銷
    revoke_bias(obj, false, false, NULL, NULL);
  } else if ((heuristics == HR_BULK_REBIAS) ||//如果是多次撤銷或多次偏向
             (heuristics == HR_BULK_REVOKE)) {
    bulk_revoke_or_rebias_at_safepoint(obj, (heuristics == HR_BULK_REBIAS), false, NULL);
  }
  clean_up_cached_monitor_info();
}

觀察程式碼我們可以發現,會根據當前偏向鎖偏向與撤銷的次數走不同的方法。這裡我們以revoke_bias()方法為例,來進行講解。具體程式碼如下:

static BiasedLocking::Condition revoke_bias(oop obj, bool allow_rebias, bool is_bulk, JavaThread* requesting_thread, JavaThread** biased_locker) {
  //省略部分程式碼...
  uint age = mark->age();
  markOop   biased_prototype = markOopDesc::biased_locking_prototype()->set_age(age);
  markOop unbiased_prototype = markOopDesc::prototype()->set_age(age);
  
  JavaThread* biased_thread = mark->biased_locker();
  if (biased_thread == NULL) {//判斷當前偏向鎖中,偏向執行緒id是否為null
    if (!allow_rebias) {//如果不允許重偏向,則使其偏向鎖不可用。
      obj->set_mark(unbiased_prototype);
    }
	//省略部分程式碼...
    return BiasedLocking::BIAS_REVOKED;
  }

 //判斷當前偏向鎖偏向的執行緒是否存在
  bool thread_is_alive = false;
  if (requesting_thread == biased_thread) {
    thread_is_alive = true;
  } else {
    ThreadsListHandle tlh;
    thread_is_alive = tlh.includes(biased_thread);
  }
  if (!thread_is_alive) {//如果當前偏向鎖偏向的執行緒不存活
    if (allow_rebias) {
      obj->set_mark(biased_prototype);//如果允許偏向,則將偏向鎖中的 執行緒id置為null
    } else {
      obj->set_mark(unbiased_prototype);//否則,將偏向鎖設定為無鎖狀態 也就是01
    }
    return BiasedLocking::BIAS_REVOKED;
  }

  //遍歷當前鎖記錄,找到擁有鎖的執行緒,將需要的displaced headers 寫到執行緒堆疊中。
  GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(biased_thread);
  BasicLock* highest_lock = NULL;
  for (int i = 0; i < cached_monitor_info->length(); i++) {
    MonitorInfo* mon_info = cached_monitor_info->at(i);
    if (oopDesc::equals(mon_info->owner(), obj)) {
      markOop mark = markOopDesc::encode((BasicLock*) NULL);
      highest_lock = mon_info->lock();
      highest_lock->set_displaced_header(mark);//將dispalece headers 寫入堆疊中
    } 	
    //省略部分程式碼...
  }
  if (highest_lock != NULL) {//將需要的displaced headers 寫到執行緒堆疊
   //省略部分程式碼...
    highest_lock->set_displaced_header(unbiased_prototype);
   //省略部分程式碼...
    obj->release_set_mark(markOopDesc::encode(highest_lock));
 
  //省略部分程式碼...
  } else {//將物件的頭恢復到未鎖定或無偏狀態
     //省略部分程式碼...
    if (allow_rebias) {
      obj->set_mark(biased_prototype);
    } else {
      // Store the unlocked value into the object's header.
      obj->set_mark(unbiased_prototype);
    }
  }
  //獲取偏向鎖指向的執行緒
  if (biased_locker != NULL) {
    *biased_locker = biased_thread;
  }

  return BiasedLocking::BIAS_REVOKED;
}

在偏向鎖的撤銷,需要等待全域性全域性點(這個時間點沒有在執行的位元組碼),它會首先暫停擁有偏向鎖的執行緒,然後檢查持有偏向鎖的執行緒是否活著,如果執行緒不處於活動狀態。會更將偏向鎖設定為無鎖狀態,如果執行緒仍然活著,擁有偏向鎖的棧 會被執行,遍歷偏向物件的鎖記錄,棧中的鎖記錄和物件頭的Mark Word要麼重新偏向於其他執行緒,要麼恢復到無鎖或者標記物件不適合作為偏向鎖,最後喚醒暫停的執行緒。

4.1.2 輕量級鎖的獲取

在上文中我們說過當monitorenter指令執行時,如果當前偏向鎖沒有開啟或多個執行緒競爭偏向鎖導致偏向鎖升級為輕量級鎖時,那麼會直接走輕量級的鎖的獲取。在講解輕量級鎖的獲取之前,需要講解一個知識點”Displaced Mark Word"。.

4.1.2.1 輕量級鎖獲與“Displaced Mark Word”

在程式碼進入同步塊,執行輕量級鎖獲取之前,如果此同步物件沒有被鎖定(鎖標誌為01狀態),JVM會在當前執行緒的幀棧中建立一個名為鎖記錄(Lock Record)的空間,用於儲存物件目前的"Mark Word"的拷貝(官方把這份拷貝加了一個Displaced字首,及Displaced Mark Word)。

虛擬機器將使用CAS操作嘗試將物件的“Mark word"更新為指向Lock Record的指標,如果這個更新動作成功了,那麼這個執行緒就擁有了該物件的鎖,及該物件處於輕量級鎖定狀態

關於輕量級鎖的獲取,具體示意圖如下:

在這裡插入圖片描述 【輕量級鎖獲與“Displaced Mark Word”】

4.1.2.2 ObjectSynchronizer::slow_enter()方法

在瞭解了具體的輕量級鎖獲取流程後,我們來檢視具體的實現slow_enter()方法。該方法是在sychronizer.cpp檔案進行宣告的。具體程式碼如下:

void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {

  markOop mark = obj->mark();//第一步 獲取鎖物件的“mark word"
  
  if (mark->is_neutral()) {//第二步,判斷當前鎖是否是無鎖狀態 後兩位標誌位為01
    //第三步,如果是無鎖狀態,儲存物件目前的“mark word"拷貝,
    //通過CAS嘗試將鎖物件Mark Word更新為指向lock Record物件的指標,
    lock->set_displaced_header(mark);
    if (mark == obj()->cas_set_mark((markOop) lock, mark)) {
      TEVENT(slow_enter: release stacklock); //如果更新成功,表示獲得鎖,則執行同步程式碼,
      return;
    }
  }
  //第四步,如果當前mark處於加鎖狀態,且執行緒幀棧中的owner指向當前鎖,則執行同步程式碼, 
  else if (mark->has_locker() &&
             THREAD->is_lock_owned((address)mark->locker())) {
    lock->set_displaced_header(NULL);
    return;
  }
  //第五步,否則說明有多個執行緒競爭輕量級鎖,輕量級鎖需要膨脹升級為重量級鎖;
  lock->set_displaced_header(markOopDesc::unused_mark());
  ObjectSynchronizer::inflate(THREAD,
                              obj(),
                              inflate_cause_monitor_enter)->enter(THREAD);
}

在輕量級鎖的獲取中,主要分為五個步驟,主要步驟如下:

  1. 第一步:markOop mark = obj->mark()方法獲取鎖物件的markOop資料mark。
  2. 第二步:mark->is_neutral()方法判斷mark是否為無鎖狀態:mark的偏向鎖標誌位為 0,鎖標誌位為 01。
  3. 第三步:如果處於無鎖狀態,儲存物件目前的“Mark Word"拷貝,通過CAS嘗試將鎖物件的“Mark Word”更新為指向lock Record物件的指標,如果更新成功,表示競爭到鎖,則執行同步程式碼。
  4. 第四步:如果處於有鎖狀態,且執行緒幀棧中的owner指向當前鎖,則執行同步程式碼,
  5. 第五步:如果都不滿足,否則說明有多個執行緒競爭輕量級鎖,輕量級鎖需要膨脹升級為重量級鎖。

適用情形:假設執行緒A和B同時執行到臨界區if (mark->is_neutral()):

  1. 執行緒AB都把Mark Word複製到各自的lock Record空間中,該資料儲存線上程的棧幀上,是執行緒私有的;
  2. 通過CAS操作保證只有一個執行緒可以把指向棧幀的指標複製到Mark Word,假設此時執行緒A執行成功,並返回繼續執行同步程式碼塊。
  3. 執行緒B執行失敗,退出臨界區,通過ObjectSynchronizer::inflate方法開始膨脹鎖(將輕量級鎖膨脹為重量級鎖)

4.1.2.3輕量級鎖的撤銷

在上文中,我們講過當走完同步塊的時候,會執行monitorexit指令,而輕量級鎖的釋放這正是在monitorexit執行的時候,也就是InterpreterRuntime::monitorexit()。

IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorexit(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
  thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
  Handle h_obj(thread, elem->obj());
  assert(Universe::heap()->is_in_reserved_or_null(h_obj()),
         "must be NULL or an object");
  if (elem == NULL || h_obj()->is_unlocked()) {
    THROW(vmSymbols::java_lang_IllegalMonitorStateException());
  }
  ObjectSynchronizer::slow_exit(h_obj(), elem->lock(), thread);
  // Free entry. This must be done here, since a pending exception might be installed on
  // exit. If it is not cleared, the exception handling code will try to unlock the monitor again.
  elem->set_obj(NULL);
#ifdef ASSERT
  thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END

在monitorexit()方法中內部會呼叫slow_exit()方法而slow_exit()方法內部會呼叫fast_exit()方法,我們檢視fast_exit()方法。

void ObjectSynchronizer::fast_exit(oop object, BasicLock* lock, TRAPS) {
  markOop mark = object->mark();
  //省略部分程式碼...
  markOop dhw = lock->displaced_header();//獲取執行緒堆疊中的Displaced Mark Word
  if (dhw == NULL) {//如果執行緒堆疊中的Displaced Mark Word為null
	#ifndef PRODUCT
    if (mark != markOopDesc::INFLATING()) {//如果當前輕量級鎖不是在膨脹為重量級鎖
      //省略部分程式碼...
      if (mark->has_monitor()) {//如果已經為重量級鎖,直接返回
        ObjectMonitor * m = mark->monitor();
        assert(((oop)(m->object()))->mark() == mark, "invariant");
        assert(m->is_entered(THREAD), "invariant");
      }
    }
#endif
    return;
  }
 
  //如果當前執行緒擁有輕量級鎖,那麼通過CAS嘗試把Displaced Mark Word替換到當前鎖物件的Mark Word,
  //如果CAS成功,說明成功的釋放了鎖
  if (mark == (markOop) lock) {
    assert(dhw->is_neutral(), "invariant");
    if (object->cas_set_mark(dhw, mark) == mark) {
      TEVENT(fast_exit: release stack-lock);
      return;
    }
  }

  //如果CAS操作失敗,說明其他執行緒在嘗試獲取輕量級鎖,這個時候需要將輕量級鎖升級為重量級鎖
  ObjectSynchronizer::inflate(THREAD,
                              object,
                              inflate_cause_vm_internal)->exit(true, THREAD);
}

在偏向鎖的釋放中,會經歷一下幾個步驟。

  1. 獲取執行緒堆疊中的Displaced Mark Word
  2. 如果執行緒堆疊中的Displaced Mark Word為null,如果已經為重量級鎖,直接返回。
  3. 如果當前執行緒擁有輕量級鎖,那麼通過CAS嘗試把Displaced Mark Word替換到當前鎖物件的Mark Word,如果CAS成功,說明成功的釋放了鎖
  4. 如果CAS操作失敗,說明其他執行緒在嘗試獲取輕量級鎖,這個時候需要將輕量級鎖升級為重量級鎖。

4.1.3 重量級鎖的獲取

在上文中我們提到過,在多個執行緒進行輕量級鎖的獲取時或在輕量級鎖撤銷時,有肯能會膨脹為重量級鎖,那現在我們就來看看膨脹的具體過程

  ObjectMonitor* ObjectSynchronizer::inflate(Thread * Self,
                                           oop object,
                                           const InflateCause cause) {
  EventJavaMonitorInflate event;

  for (;;) {//開始自旋
    const markOop mark = object->mark();
    // The mark can be in one of the following states:
    // *  Inflated     - just return
    // *  Stack-locked - coerce it to inflated
    // *  INFLATING    - busy wait for conversion to complete
    // *  Neutral      - aggressively inflate the object.
    // *  BIASED       - Illegal.  We should never see this

    //1.如果當前鎖已經為重量級鎖了,直接返回
    if (mark->has_monitor()) {
      ObjectMonitor * inf = mark->monitor();
      return inf;
    }


    //2.如果正在膨脹的過程中,在完成膨脹過程中,其他執行緒必須等待。
    if (mark == markOopDesc::INFLATING()) {
      TEVENT(Inflate: spin while INFLATING);
      ReadStableMark(object);
      continue;
    }

	//3.如果當前為輕量級鎖,迫使其膨脹為重量級鎖
    if (mark->has_locker()) {
      ObjectMonitor * m = omAlloc(Self);
      m->Recycle();
      m->_Responsible  = NULL;
      m->_recursions   = 0;
      m->_SpinDuration = ObjectMonitor::Knob_SpinLimit;   // Consider: maintain by type/class

      markOop cmp = object->cas_set_mark(markOopDesc::INFLATING(), mark);
      if (cmp != mark) {
        omRelease(Self, m, true);
        continue;       // Interference -- just retry
      }

      markOop dmw = mark->displaced_mark_helper();
      assert(dmw->is_neutral(), "invariant");

      m->set_header(dmw);

     
      m->set_owner(mark->locker());
      m->set_object(object);
      // TODO-FIXME: assert BasicLock->dhw != 0.

      // Must preserve store ordering. The monitor state must
      // be stable at the time of publishing the monitor address.
      guarantee(object->mark() == markOopDesc::INFLATING(), "invariant");
      object->release_set_mark(markOopDesc::encode(m));

      // Hopefully the performance counters are allocated on distinct cache lines
      // to avoid false sharing on MP systems ...
      OM_PERFDATA_OP(Inflations, inc());
      TEVENT(Inflate: overwrite stacklock);
      if (log_is_enabled(Debug, monitorinflation)) {
        if (object->is_instance()) {
          ResourceMark rm;
          log_debug(monitorinflation)("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
                                      p2i(object), p2i(object->mark()),
                                      object->klass()->external_name());
        }
      }
      if (event.should_commit()) {
        post_monitor_inflate_event(&event, object, cause);
      }
      return m;
    }

	//4.如果為無鎖狀態,重置監視器狀態
    assert(mark->is_neutral(), "invariant");
    ObjectMonitor * m = omAlloc(Self);
    // prepare m for installation - set monitor to initial state
    m->Recycle();
    m->set_header(mark);
    m->set_owner(NULL);
    m->set_object(object);
    m->_recursions   = 0;
    m->_Responsible  = NULL;
    m->_SpinDuration = ObjectMonitor::Knob_SpinLimit;       // consider: keep metastats by type/class

    if (object->cas_set_mark(markOopDesc::encode(m), mark) != mark) {
      m->set_object(NULL);
      m->set_owner(NULL);
      m->Recycle();
      omRelease(Self, m, true);
      m = NULL;
      continue;
 
    }
  //省略部分程式碼...
   return m ;
}

在輕量級鎖膨脹為重量級鎖大致可以分為以下幾個過程

  1. 如果當前鎖已經為重量級鎖了,直接返回ObjectMonitor 物件。
  2. 如果正在膨脹的過程中,在完成膨脹過程中,其他執行緒自旋等待。這裡需要注意一點,雖然是自旋操作,但不會一直佔用cpu資源,會通過spin/yield/park方式掛起執行緒。
  3. 如果當前為輕量級鎖,迫使其膨脹為重量級鎖
  4. 如果是無鎖,重置ObjectMonitor 中的狀態。

4.1.4 鎖升級示意圖

在這裡插入圖片描述 【偏向鎖獲得和撤銷】

在這裡插入圖片描述 【輕量級鎖膨脹流程圖】

4.1.5 重量級鎖的競爭

在上文中,我們主要介紹了整個鎖升級的流程與原始碼實現。而真正執行緒的等待與競爭我們還沒有詳細描述。

下面我們就來講講當鎖膨脹為重量級鎖的時候,整個執行緒的競爭與等待過程。

重量級鎖的競爭是在objectMonitor.cpp中ObjectMonitor::enter()方法中實現的。

簡述整個過程,可以是根據虛擬機器規範的要求,在執行monitorenter指令時:

  • 首先要嘗試獲取物件的鎖。
  • 如果這個物件沒被鎖定,或者當前執行緒已經擁有了那個物件的鎖,把鎖的計數器加1。相應的,在執行monitorexit指令時會將鎖計數器減1,當計數器為0時,鎖就被釋放。
  • 如果獲取物件鎖失敗,那當前執行緒就要阻塞等待,直到物件鎖被另外一個執行緒釋放為止。

虛擬機器規範對monitorenter和monitorexit的行為描述中,有兩點是需要特別注意的。

  1. synchronized同步塊對同一條執行緒來說是可重入的,不會出現自己把自己鎖死的問題
  2. 同步塊在已進入的執行緒執行完之前,會阻塞後面其他執行緒的進入。

4.1.5.1 ObjectMonitor結構

在講解具體的鎖獲取之前,我們需要了解每個鎖物件(這裡指已經升級為重量級鎖的物件)都有一個ObjectMonitor(物件監視器)。也就是說每個執行緒獲取鎖物件都會通過ObjectMonitor

class ObjectMonitor {
 public:
  enum {
    OM_OK,                    // 沒有錯誤
    OM_SYSTEM_ERROR,          // 系統錯誤
    OM_ILLEGAL_MONITOR_STATE, // 監視器狀態異常
    OM_INTERRUPTED,           // 當前執行緒已經中斷
    OM_TIMED_OUT              // 執行緒等待超時
  };
  volatile markOop   _header;       // 執行緒幀棧中儲存的 鎖物件的mark word拷貝

 protected:                         // protected for JvmtiRawMonitor
  void *  volatile _owner;          // 指向獲得objectMonitor的執行緒或者 BasicLock物件
  volatile jlong _previous_owner_tid;  // 上一個獲得objectMonitor的執行緒id
  volatile intptr_t  _recursions;   // 同一執行緒重入鎖的次數,如果是0,表示第一次進入
  ObjectWaiter * volatile _EntryList; // 在進入或者重進入阻塞狀態下的執行緒連結串列
                             
 protected:
  ObjectWaiter * volatile _WaitSet; // 處於等待狀態下的執行緒連結串列 ObjectWaiter
  volatile jint  _waiters;          //處於等待狀態下的執行緒個數
}  

4.1.5.2 重量級鎖的獲取

在瞭解了ObjectMonitor 類中具體結構後,來看看具體的鎖獲取方法ObjectMonitor::enter(),具體程式碼如下所示:

void ObjectMonitor::enter(TRAPS) {

  Thread * const Self = THREAD;//當前進入enter方法的執行緒
 
 //通過CAS操作嘗試吧monitor的_owner( 指向獲得objectMonitor的執行緒或者 BasicLock物件)設定為當前執行緒
  void * cur = Atomic::cmpxchg(Self, &_owner, (void*)NULL);
  
  if (cur == NULL) {//如果成功,當前執行緒獲取鎖成功,直接執行同步程式碼塊
    assert(_recursions == 0, "invariant");
    assert(_owner == Self, "invariant");
    return;
  }
  
  //如果是同一執行緒,則記錄當前重入的次數(上一步CAS操作不管成功還是失敗,都會返回_owner指向的地址)
  if (cur == Self) {
    _recursions++;
    return;
  }

  //如果之前_owner指向的BasicLock在當前執行緒棧上,說明當前執行緒是第一次進入該monitor,
  //設定_recursions為1,_owner為當前執行緒,該執行緒成功獲得鎖並返回;
  if (Self->is_lock_owned ((address)cur)) {
    assert(_recursions == 0, "internal state error");
    _recursions = 1;
    _owner = Self;
    return;
  }
  //省略部
  分程式碼...
  
  //開始競爭鎖
    for (;;) {
      jt->set_suspend_equivalent();
      EnterI(THREAD);
      if (!ExitSuspendEquivalent(jt)) break;
      _recursions = 0;
      _succ = NULL;
      exit(false, Self);
      jt->java_suspend_self();
    }
    Self->set_current_pending_monitor(NULL);

  }
//省略部分程式碼...
}

在重量級級鎖的競爭步驟,主要分為以下幾個步驟:

  • 通過CAS操作嘗試吧monitor的_owner( 指向獲得objectMonitor的執行緒或者 BasicLock物件)設定為當前執行緒,如果CAS操作成功,表示執行緒獲取鎖成功,直接執行同步程式碼塊。
  • 如果是同一執行緒重入鎖,則記錄當前重入的次數。
  • 如果2,3步驟都不滿足,則開始競爭鎖,走EnterI()方法。

EnterI()方法實現如下:

  1. 把當前執行緒被封裝成ObjectWaiter的node物件,同時將該執行緒狀態設定為TS_CXQ(競爭狀態)
  2. 在for迴圈中,通過CAS把node節點push到_cxq連結串列中,如果CAS操作失敗,繼續嘗試,是因為當期_cxq連結串列已經發生改變了繼續for迴圈,如果成功直接返回。
  3. 將node節點push到_cxq連結串列之後,通過自旋嘗試獲取鎖(TryLock方法獲取鎖),如果迴圈一定次數後,還獲取不到鎖,則通過park函式掛起。(並不會消耗CPU資源)
void ObjectMonitor::EnterI(TRAPS) {
  Thread * const Self = THREAD;
  //省略部分程式碼...
  
  //把當前執行緒被封裝成ObjectWaiter的node物件,狀態設定成ObjectWaiter::TS_CXQ;
  ObjectWaiter node(Self);
  Self->_ParkEvent->reset();
  node._prev   = (ObjectWaiter *) 0xBAD;
  node.TState  = ObjectWaiter::TS_CXQ;//TS_CXQ:為競爭鎖狀態

 //在for迴圈中,通過CAS把node節點push到_cxq連結串列中;
  ObjectWaiter * nxt;
  for (;;) {
    node._next = nxt = _cxq;
    //如果CAS操作失敗,繼續嘗試,是因為當期_cxq連結串列已經發生改變了
    if (Atomic::cmpxchg(&node, &_cxq, nxt) == nxt) break;
	//有可能在放入_cxq連結串列中時,已經獲取到鎖了,直接返回
    if (TryLock (Self) > 0) {
      assert(_succ != Self, "invariant");
      assert(_owner == Self, "invariant");
      assert(_Responsible != Self, "invariant");
      return;
    }
  }
 //將node節點push到_cxq連結串列之後,通過自旋嘗試獲取鎖
  for (;;) {

    if (TryLock(Self) > 0) break;//嘗試獲取鎖
    assert(_owner != Self, "invariant");

    if ((SyncFlags & 2) && _Responsible == NULL) {
      Atomic::replace_if_null(Self, &_Responsible);
    }
    //判斷執行迴圈的次數,如果執行相應迴圈後,如果還是沒有獲取到鎖,則通過park函式將當前執行緒掛起,等待被喚醒
    if (_Responsible == Self || (SyncFlags & 1)) {
      TEVENT(Inflated enter - park TIMED);
      Self->_ParkEvent->park((jlong) recheckInterval);
      // Increase the recheckInterval, but clamp the value.
      recheckInterval *= 8;
      if (recheckInterval > MAX_RECHECK_INTERVAL) { 其中MAX_RECHECK_INTERVAL為1000
        recheckInterval = MAX_RECHECK_INTERVAL;
      }
    } else {
      TEVENT(Inflated enter - park UNTIMED);
      Self->_ParkEvent->park();
    }
	//省略部分程式碼...
    OrderAccess::fence();
  }
 //省略部分程式碼...
  return;
}


//關於獲取鎖的TryLock方法如下所示:就是將鎖中的_owner指標指向當前執行緒,如果成功返回1,反之返回-1
int ObjectMonitor::TryLock(Thread * Self) {
  void * own = _owner;
  if (own != NULL) return 0;
  if (Atomic::replace_if_null(Self, &_owner)) {
    return 1;
  }
  return -1;
}

4.1.5.3 重量級鎖的釋放

void ObjectMonitor::exit(bool not_suspended, TRAPS) {
  Thread * const Self = THREAD;
  if (THREAD != _owner) {//如果當前鎖物件中的_owner沒有指向當前執行緒
    if (THREAD->is_lock_owned((address) _owner)) {
      //但是_owner指向的BasicLock在當前執行緒棧上,那麼將_owner指向當前執行緒
      assert(_recursions == 0, "invariant");
      _owner = THREAD;
      _recursions = 0;
    } else {
	  //省略部分程式碼...
      return;
    }
  }
  
  //如果當前,執行緒重入鎖的次數,不為0,那麼就重新走ObjectMonitor::exit,直到重入鎖次數為0為止
  if (_recursions != 0) {
    _recursions--;        // this is simple recursive enter
    TEVENT(Inflated exit - recursive);
    return;
  }
  //省略部分程式碼...
  for (;;) {

    if (Knob_ExitPolicy == 0) {
      OrderAccess::release_store(&_owner, (void*)NULL);   //釋放鎖
      OrderAccess::storeload();                        // See if we need to wake a successor
      if ((intptr_t(_EntryList)|intptr_t(_cxq)) == 0 || _succ != NULL) {
        TEVENT(Inflated exit - simple egress);
        return;
      }
      TEVENT(Inflated exit - complex egress);
    //省略部分程式碼...
    }
    //省略部分程式碼...
    ObjectWaiter * w = NULL;
    int QMode = Knob_QMode;
	
	//根據QMode的模式判斷,
	
    //如果QMode == 2則直接從_cxq掛起的執行緒中喚醒	
    if (QMode == 2 && _cxq != NULL) {
      w = _cxq;
      ExitEpilog(Self, w);
      return;
	    }
     //省略部分程式碼... 省略的程式碼為根據QMode的不同,不同的喚醒機制
	 }
   } 
   //省略部分程式碼...
}

重量級鎖的釋放可以分為以下步驟:

  • 判斷當前鎖物件中的_owner沒有指向當前執行緒,如果_owner指向的BasicLock在當前執行緒棧上,那麼將_owner指向當前執行緒。
  • 如果當前鎖物件中的_owner指向當前執行緒,則判斷當前執行緒重入鎖的次數,如果不為0,那麼就重新走ObjectMonitor::exit(),直到重入鎖次數為0為止。
  • 釋放當前鎖,並根據QMode的模式判斷,是否將_cxq中掛起的執行緒喚醒。還是其他操作。