首頁 >Java >java教程 >Java關鍵字synchronized原理與鎖定的狀態實例分析

Java關鍵字synchronized原理與鎖定的狀態實例分析

王林
王林轉載
2023-05-11 15:25:06763瀏覽

一、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"如下:

Java關鍵字synchronized原理與鎖定的狀態實例分析

2、鎖的狀態變化

(1) 無鎖定→ 輕量級鎖定

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

示範流程如下:

Java關鍵字synchronized原理與鎖定的狀態實例分析

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

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

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

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

示範流程如下:

Java關鍵字synchronized原理與鎖定的狀態實例分析

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

(3) 關於偏向鎖

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

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

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

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

Java關鍵字synchronized原理與鎖定的狀態實例分析

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

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

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

(4) 完整的鎖升級過程

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

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

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

  • 當鎖被佔用時,會升級到輕量級鎖,然後由輕量級鎖定升級到重量級鎖定。

Java關鍵字synchronized原理與鎖定的狀態實例分析

以上是Java關鍵字synchronized原理與鎖定的狀態實例分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除