併發編程之Synchronized

訪問 https://www.idwarf.cn 獲取更多java內容

概念

synchronized 是 Java 中的關鍵字,是利用鎖的機制來實現同步的。

鎖機制有如下兩種特性:

  • 互斥性:即在同一時間只允許一個線程持有某個對象鎖,通過這種特性來實現多線程中的協調機制,這樣在同一時間只有一個線程對需同步的代碼塊(複合操作)進行訪問。互斥性我們也往往稱爲操作的原子性。

  • 可見性:必須確保在鎖被釋放之前,對共享變量所做的修改,對於隨後獲得該鎖的另一個線程是可見的(即在獲得鎖時應獲得最新共享變量的值),否則另一個線程可能是在本地緩存的某個副本上繼續操作從而引起不一致。

對象鎖

在 Java 中,每個對象都會有一個 monitor 對象,這個對象其實就是 Java 對象的鎖,通常會被稱爲“內置鎖”或“對象鎖”。類的對象可以有多個,所以每個對象有其獨立的對象鎖,互不干擾。
某一線程佔有這個對象的時候,先詢問monitor的計數器是不是0,如果是0還沒有線程佔有,此時這個線程可佔有這個對象,並且對這個對象的monitor+1;如果不爲0,表示這個線程已經被其他線程佔有,這個線程進入等待狀態。當線程釋放佔有權的時候,monitor-1;
可重入:某個線程已經獲得某個鎖,可以再次獲取鎖而不會出現死鎖。

synchronized (this) {
	System.out.println("第1次獲取鎖,這個鎖是:" + this);
	int index = 1;
	while (i<10) {
		synchronized (this) {
			System.out.println("第" + (++index) + "次獲取鎖,這個鎖是:" + this);
		}
		i++;
	}
}

類鎖

在 Java 中,針對每個類也有一個鎖,可以稱爲“類鎖”,類鎖實際上是通過對象鎖實現的,即類的 Class 對象鎖。每個類只有一個 Class 對象,所以每個類只有一個類鎖。

Synchronized用法

根據修飾對象分類:

  • 修飾代碼塊

synchronized(this|object) {}
synchronized(類.class) {}

  • 修飾方法
  1. 修飾非靜態方法
public synchronized void methodName(){
……
}
  1. 修飾靜態方法
public synchronized static void methodName(){
……
}

根據獲取的鎖分類:

  • 獲取對象鎖:

synchronized(this|object) {}
修飾非靜態方法

  • 獲取類鎖

synchronized(類.class) {}
修飾靜態方法

  1. 在代碼塊上加鎖:使用javap(javap -V filename)通過反編譯Synchronized所在的類可以發現,在Synchronized所使用的方法被monitorenter和monitorexit包含,注意monitorexit有兩個,一個爲正常結束時的出口,一個爲異常結束時的出口。
  2. 在方法上加鎖:方法上有個flag,值爲ACC_SYNCHRONIZED表示此方法具有互斥性。

JDK對synchronized的優化。

  1. jdk1.6之前,對於最原始的synchronized關鍵字,鎖被稱之爲重量級鎖。
  2. jdk1.6之後,鎖分爲以下幾種:
  • 無鎖狀態,無鎖。
  • 偏向鎖,指一段同步代碼一直被同一個線程所訪問,那麼該線程會自動的獲取鎖。降低獲取鎖的代價。
  • 輕量級鎖,當鎖是偏向鎖的時候,被另一個線程所訪問,偏向鎖就會升級爲輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞,提高性能。
  • 重量級鎖,當鎖爲輕量級鎖的時候,另一個線程雖然是自旋,但自旋不會一直持續下去,當自旋一定次數的時候,還沒獲取到鎖就會進入阻塞,該鎖膨脹爲重量級鎖。重量級會讓其他申請線程阻塞,性能降低。
    [外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-kMMKFucx-1594090835870)(https://www.idwarf.cn/upload/2020/07/image-323ef3c6a27a42aba6132e0209720552.png)]
    偏向鎖,輕量級鎖都是樂觀鎖,重量級鎖是悲觀鎖。
    一個對象剛開始實例化的時候,沒有任何線程來訪問它的時候。它是可偏向的,意味着,它現在認爲只可能有一個線程來訪問它,所以當第一個線程來訪問它的時候,它會偏向這個線程,此時,對象持有偏向鎖。偏向第一個線程,這個線程在修改對象頭成爲偏向鎖的時候使用CAS操作(下文提及),並將對象頭中的ThreadID改成自己的ID,之後再次訪問這個對象時,只需要對比ID,不需要再使用CAS在進行操作。
    一旦有第二個線程訪問這個對象,因爲偏向鎖不會主動釋放,所以第二個線程可以看到對象時偏向狀態,這時表明在這個對象上已經存在競爭了,檢查原來持有該對象鎖的線程是否依然存活,如果掛了,則可以將對象變爲無鎖狀態,然後重新偏向新的線程,如果原來的線程依然存活,則馬上執行那個線程的操作棧,檢查該對象的使用情況,如果仍然需要持有偏向鎖,則偏向鎖升級爲輕量級鎖,(偏向鎖就是這個時候升級爲輕量級鎖的)。如果不存在使用了,則可以將對象回覆成無鎖狀態,然後重新偏向。
    輕量級鎖認爲競爭存在,但是競爭的程度很輕,一般兩個線程對於同一個鎖的操作都會錯開,或者說稍微等待一下(執行幾次空循環,自旋),另一個線程就會釋放鎖。 但是當自旋超過一定的次數,或者一個線程在持有鎖,一個在自旋,又有第三個來訪時,輕量級鎖膨脹爲重量級鎖,重量級鎖使除了擁有鎖的線程以外的線程都阻塞,防止CPU空轉。

注:此段解釋引用於:https://blog.csdn.net/cuichunchi/article/details/88532582

鎖消除

虛擬機即時編譯器在運行時,對一些代碼上要求同步,但是被檢測到不可能存在共享數據競爭的鎖進行削除。
如下,synchronized代碼段操作本身具有原子性,不需要使用synchronized:

synchronized(this){
int i = 10;
}

CAS

Compare-and-Swap. 在sun.misc.Unsafe類中有相關的方法。其底層是用 C/C++ 實現的,所以它的方式都是被 native 關鍵字修飾過的。Unsafe是CAS的核心類,需要通過本地方法來訪問,由於Java方法無法直接訪問底層系統,Unsafe相當於一個後門,基於該類可以直接操作特定內存的數據。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-jLTP3QdD-1594090835873)(https://www.idwarf.cn/upload/2020/07/image-0398fc32c16b483d941e649c22b32ab7.png)]

CAS 的思想很簡單:三個參數,一個當前內存值 V、舊的預期值 A、即將更新的值 B,當且僅當預期值 A 和內存值 V 相同時,將內存值修改爲 B 並返回 true,否則什麼都不做,並返回 false。

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