首頁 > 軟體

Java關鍵字synchronized原理與鎖的狀態詳解

2022-08-10 14:04:04

一、Java中鎖的概念

  • 自旋鎖:是指當一個執行緒獲取鎖的時候,如果鎖已經被其它執行緒獲取,那麼該執行緒將回圈等待,然後不斷的判斷鎖是否能被成功獲取,直到獲取到鎖才會退出迴圈。
  • 樂觀鎖:假定沒有衝突,在修改資料時如果發現資料和之前獲取的不一致,則讀最新資料,重試修改。
  • 悲觀鎖:假定會發生並行衝突,同步所有對資料的相關操作,從讀資料就開始上鎖。
  • 獨享鎖(寫):給資源加上寫鎖,執行緒可以修改資源,其它執行緒不能再加鎖(單寫)。
  • 共用鎖(讀):給資源加上讀鎖後只能讀不能修改,其它執行緒也只能加讀鎖,不能加寫鎖(多度)。看成Semaphore(號誌)理解即可。
  • 可重入鎖&不可重入鎖:執行緒拿到一把鎖之後,可以自由進入同一把鎖所同步的其它程式碼。
  • 公平鎖&非公平鎖:爭搶鎖的順序,如果是按先來後到,則為公平。即能保證搶鎖的順序和搶到鎖的順序一致則為公平鎖。

二、同步關鍵字synchronized特性

特性:可重入、獨享、悲觀鎖。

鎖相關的優化:

  • 鎖消除 :開啟鎖消除的引數有 -XX:+DoEscapeAnalysis-XX:+EliminateLocks
  • 鎖粗化:JDK做了鎖粗化的優化,但我們自己可從程式碼層面優化。

1、鎖消除範例

/**
 * 鎖消除範例,JIT即時編譯,進行了鎖消除
 * @author 劉亞樓
 * @date 2020/1/16
 */
public class LockEliminationExample {
	/**
	 * StringBuilder執行緒不安全,StringBuffer用了synchronized關鍵字,是執行緒安全的
	 * 針對下面這種單執行緒加鎖、解鎖操作,JIT會進行優化,進行鎖消除
	 */
	public static void eliminateLock() {
		StringBuffer stringBuffer = new StringBuffer();
		stringBuffer.append("a");
		stringBuffer.append("b");
		stringBuffer.append("c");
		stringBuffer.append("a");
		stringBuffer.append("b");
		stringBuffer.append("c");
		stringBuffer.append("a");
		stringBuffer.append("b");
		stringBuffer.append("c");
	}
}

2、鎖粗化範例

/**
 * 鎖粗化範例
 * @author 劉亞樓
 * @date 2020/1/16
 */
public class LockCoarseningExample {
	/**
	 * 針對下面這種無意義的加鎖操作,JIT會進行優化,對變數i的所有操作放到一個同步程式碼塊裡
	 */
	public static void lockCoarsening() {
		int i = 0;
		synchronized (LockCoarseningExample.class) {
			i++;
		}
		synchronized (LockCoarseningExample.class) {
			i--;
		}
		synchronized (LockCoarseningExample.class) {
			i++;
		}
		synchronized (LockCoarseningExample.class) {
			i++;
			i--;
			i++;
		}
	}
}

備註:鎖消除和鎖粗化的區別在於鎖消除是針對單個執行緒重複加解鎖做的優化,最終沒有鎖的存在。而鎖粗化不只是針對單執行緒,且最終還是有鎖的存在。

三、synchronized關鍵字原理

1、關於Mark Word

首先,物件在堆中由物件頭、範例資料和對齊填充組成。

物件頭包含兩部分資訊,第一部分用於儲存物件自身的執行時資料,如雜湊碼、GC分代年齡、鎖狀態標誌、執行緒持有的鎖、偏向鎖id等,這部分資料官方稱為"Mark Word"。

物件頭的另一部分是型別指標,即物件指向它的類後設資料的指標,虛擬機器器通過這個指標來確定這個物件是哪個類的範例。

synchronized實現的鎖是通過改變物件頭的"Mark Word"來實現的。

"Mard Word"在32位元和64位元的虛擬機器器(未開啟壓縮指標)中分別為32位元和64位元。32位元虛擬機器器"Mark Word"如下:

2、鎖的狀態變化

(1) 無鎖 → 輕量級鎖

無鎖變成輕量級鎖時,多個執行緒會讀取物件的物件頭的無鎖狀態mark word內容,然後進行cas操作進行修改,預期值是無鎖狀態mark word內容,新值是輕量級鎖狀態mark word內容,若修改成功,Lock record address指向成功獲取鎖的執行緒的Lock Record

演示流程如下:

(2) 輕量級鎖 → 重量級鎖

由於未成功獲取鎖的執行緒會自旋,長時間自旋會消耗CPU資源,因此自旋到一定次數會進行鎖升級,由輕量級鎖轉變為重量級鎖。

重量級鎖是通過object monitor(物件監視器)實現的,物件監視器包括entryList(鎖池)、owner(持鎖者)、waitSet(等待集合)等。

升級為重量級鎖時物件頭mark word的內容是monitor address(物件監視器地址),指向物件監視器。

演示流程如下:

備註:搶鎖失敗執行緒會進入entryList(鎖池),在呼叫wait方法後,執行緒會進入waitSet(等待集合),waitSet中的執行緒被喚醒後會重新進入entryList。

(3) 關於偏向鎖

加鎖之後不解鎖,針對單執行緒

所謂偏向就是偏心,單執行緒加鎖之後就不再解鎖,減少了加鎖→業務處理→釋放鎖→加鎖操作流程。

在JDK6以後,預設已經開啟了偏向鎖這個優化,通過JVM引數-XX:-UseBiasedLocking來禁用偏向鎖,若偏向鎖開啟,只有一個執行緒搶鎖,可獲取到偏向鎖。

關於偏向鎖Mark Word內容如下:

偏向標記第一次有用,出現過爭用後就沒用了。

偏向鎖本質就是無鎖,如果沒有發生過任何多執行緒爭搶鎖的情況,JVM認為就是單執行緒,無需做同步。

備註:JVM為了少幹活,同步在JVM底層是有很多操作來實現的,如果沒有爭用,就不需要去做同步操作。

(4) 完整的鎖升級過程

如果未開啟偏向鎖,無鎖狀態會先升級為輕量級鎖,輕量級鎖自選到一定程度升級為重量級鎖。

如果開啟了偏向鎖,有兩種情況:

  • 當鎖未被佔用時,會升級為無鎖,無鎖再升級為輕量級鎖,再由輕量級鎖升級為重量級鎖。
  • 當鎖被佔用時,會升級為輕量級鎖,再由輕量級鎖升級到重量級鎖。

到此這篇關於Java關鍵字synchronized原理與鎖的狀態詳解的文章就介紹到這了,更多相關Java synchronized內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com