Java 併發編程(四)JVM中鎖的優化

前提知識—Java對象頭(Mark Word)

鎖的優化

偏向鎖、輕量級鎖、重量級鎖三者各自的應用場景

  • 偏向鎖:只有一個線程進入臨界區
  • 輕量級鎖:多個線程交替進入臨界區
  • 重量級鎖:多個線程同時進入臨界區

偏向鎖、輕量級鎖都是JVM引入的鎖優化手段,目的是降低線程同步的開銷。比如以下的同步代碼塊:

synchronized (lockObject) {
	// do something
}

上述同步代碼塊中存在一個臨界區,假設當前存在Thread#1和Thread#2這兩個用戶線程,分三種情況來討論:

情況一:只有Thread#1會進入臨界區;
情況二:Thread#1和Thread#2交替進入臨界區;
情況三:Thread#1和Thread#2同時進入臨界區。

情況一

上述的情況一是偏向鎖的適用場景,此時當Thread#1進入臨界區時,JVM會將lockObject的對象頭Mark Word的鎖標誌位設爲“1 01”,同時會用CAS操作把Thread#1的線程ID記錄到Mark Word中,此時進入偏向模式。所謂“偏向”,指的是這個鎖會偏向於Thread#1,若接下來沒有其他線程進入臨界區,則Thread#1再出入臨界區無需再執行任何同步操作。也就是說,若只有Thread#1會進入臨界區,實際上只有Thread#1初次進入臨界區時需要執行CAS操作,以後再出入臨界區都不會有同步操作帶來的開銷(只需要比較對象頭Mark Word中線程ID是否爲Thread#1)。

情況二

然而情況一是一個比較理想的情況,更多時候Thread#2也會嘗試進入臨界區。若Thread#2嘗試進入時:
在Thread#1已退出臨界區的情況(Thread#1不會主動去釋放偏向鎖),這時Thread#2發現Mark Word的線程ID並不是自己,說明偏向鎖上發生了競爭(對應情況二),此時會撤銷偏向,Mark Word中不再存放偏向線程ID,而是存放hashCodeGC分代年齡,同時鎖對象標識位變爲“0 01”(表示未鎖定)。
這時Thread#2會來獲取lockObject的輕量級鎖,因爲此時Thread#1和Thread#2交替進入臨界區,所以偏向鎖無法滿足需求,需要膨脹到輕量級鎖。

虛擬機在當前線程Thread#2的虛擬機棧中創建 Lock Record(棧中鎖記錄),Lock Record用來保存Mark Word的拷貝,然後使用CAS 操作將lockObjectMark Word 更新爲 指向Lock Record 的指針。如果 CAS操作成功了,那麼線程就獲取了該對象上的鎖,並且將lockObjectMark Word 的鎖標記變爲 00,表示該對象處於輕量級鎖狀態(輕量級鎖只能向上升級爲重量級鎖,或者直接解鎖)。
Thread#2在退出臨界區時(即同步完成)會通過CAS操作嘗試將Mark Word的內容恢復(即解鎖回到未鎖定,但不可偏向的的鎖狀態“0 01”),會出現下面兩種情況:

  • Mark Word中的Lock Record指針並不指向Thread#2虛擬機棧的Lock Record,說明發生了競爭,CAS失敗,輕量級鎖膨脹爲重量級鎖(即情況3)
  • Mark Word中的Lock Record指針指向Thread#2虛擬機棧的Lock Record,順利解鎖,Thread#1進入臨界區的時候就可以順利獲取輕量級鎖。

    輕量級鎖CAS操作之前堆棧與對象的狀態

    輕量級鎖CAS操作之後堆棧與對象的狀態

情況三

若一直是Thread#1和Thread#2交替進入臨界區,那麼沒有問題,輕量鎖hold得住。一旦在輕量級鎖上發生競爭,即出現“Thread#1和Thread#2同時進入臨界區”的情況,輕量級鎖就hold不住了。 需要使用互斥量來確保同步。

重量級鎖、輕量級鎖和偏向鎖之間轉換


該圖主要是對上述內容的總結,如果對上述內容有較好的瞭解的話,該圖應該很容易看懂。

Java 併發編程(一)Volatile原理剖析及使用
Java 併發編程(二)Synchronized原理剖析及使用
Java 併發編程(三)Synchronized底層優化(偏向鎖與輕量級鎖)
Java 併發編程(四)JVM中鎖的優化
Java 併發編程(五)原子操作類

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