# Synchronized 原理和優化

synchronized 原理和優化

synchronized 是java 中解決併發問題最常用的方法,也是最簡單的一種方法。Synchronized 的主要作用是

  1. 確保線程互斥訪問同步代碼
  2. 保證共享變量的修改能及時可見
  3. 有效解決重排問題

Synchronized 原理

反編譯下面代碼看看Synchronized 是如何實現代碼同步的

public class Demo{
	public void method(){
		sychronized(this){
			System.out.println("start");
		}
	}
}

反編譯(javap -verbose Demo.class)結果

  stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: ldc           #3                  // String start
         9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        12: aload_1
        13: monitorexit
        14: goto          22
        17: astore_2
        18: aload_1
        19: monitorexit
        20: aload_2
        21: athrow
        22: return

關於 monitorenter 和 monitorexit倆條指令的作用,參考JVM 規範

Java 虛擬機中的同步(synchronization) 基於進入和退出monitor對象實現,無論是顯示同步(有明確的monitorenter和 monitorexit指令,即同步代碼塊)還是隱式同步都是如此。在java 語言中,同步用的最多的地方可能是synchronzied 修飾的同步方法。同步方法並不是由monitorenter 和 monitorexit 指令來實現同步的,而是由方法調用指令讀取運行時常量池中方法表結構的 ACC_SYNCHRONIZED 標誌來隱式實現的。

同步代碼塊:monitorenter指令插入到同步代碼塊的開始位置,monitorexit指令插入到同步代碼塊的結束位置,JVM需要保證每一個monitorenter都有一個monitorexit與之相對應。任何一個兌現都有一個monitor與之關聯,當且一個monitor被持有之後,他將處於被鎖定狀態。線程執行到monitorenter 指令時,將會獲取對象所對應的monitor 所有權,即嘗試獲取對象的鎖。

在jvm 中對象在內存中分爲三塊區域:對象頭、實例變量和填充數據

實例變量:存放類的屬性數據信息,包括父類的屬性信息,如果是數組的實例部分還包括數組的長度,這部分內存按4字節對齊。

填充數據:由於虛擬機要求對象起始地址必須是8字節的整數倍。填充數據不是必須存在的,僅僅是爲了字節對齊,這點了解即可。

對象頭:Hotspot虛擬機的對象頭主要包括兩部分數據:Mark Word(標記字段)、Klass Pointer(類型指針)。其中Klass Point是是對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例,Mark Word用於存儲對象自身的運行時數據,它是實現輕量級鎖和偏向鎖的關鍵。

Mark Word:用於存儲對象自身的運行時數據,如哈希碼(HashCode)、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程 ID、偏向時間戳等等。Java對象頭一般佔有兩個機器碼(在32位虛擬機中,1個機器碼等於4字節,也就是32bit),但是如果對象是數組類型,則需要三個機器碼,因爲JVM虛擬機可以通過Java對象的元數據信息確定Java對象的大小,但是無法從數組的元數據來確認數組的大小,所以用一塊來記錄數組長度。

Monitor: 我們可以把它理解爲一個同步工具,也可是一種同步機制,通常被描述爲一個對象。與一切對象一樣,所有的Java 對象自帶Monitor,每一個Java 對象都有成爲Monitor 的潛質,因爲在Java的設計中,每一個Java 對象都有一把內部(Monitor)鎖。Monitor 是線程私有的數據結構,每一個線程都有一個可用Monitor record 列表,同時還有一個全局可用列表。每一個被鎖住的對象會和monitor 關聯,同時Monitor中有一個Owner 字段存放該鎖的線程的唯一標識,表示該所被線程佔用。結構如下:

在這裏插入圖片描述

Owner:初始時爲NULL表示當前沒有任何線程擁有該monitor record,當線程成功擁有該鎖後保存線程唯一標識,當鎖被釋放時又設置爲NULL

EntryQ: 關聯一個系統互斥鎖(semaphore),阻塞所有試圖monitor record 失敗線程

RcThis: 表示blocked 或waiting 在該monitor record 上所有線程的個數

Nest: 用來實現重入鎖的計數

HashCode: 保存從對象頭拷貝過來的HashCode值

Candidate: 爲每一次只有一個線程能夠成功擁有鎖,如果每次前一個釋放鎖的線程喚醒所有正在阻塞或等待的線程,會引起不必要的上下文切換(從阻塞到就緒然後因爲競爭鎖失敗又被阻塞)從而導致性能嚴重下降。Candidate只有兩種可能的值0表示沒有需要喚醒的線程1表示要喚醒一個繼任線程來競爭鎖。

Java 虛擬即對synchronized 的優化

在這裏插入圖片描述

鎖的狀態總共四種,無鎖狀態、偏向鎖、輕量級鎖、重量級鎖。隨着鎖的競爭可以衝偏向鎖升級到輕量級鎖,再次升級爲重量級鎖。鎖升級是單向的,也就是說只能從低到高升級,不會出現鎖降級。

偏向鎖

偏向鎖是jdk 6 之後引入的新鎖,是對加鎖操作的一種優化。大多數情況下,所不僅不存在多線程的競爭,而且總是有同一線程多次獲得,因此爲了減少同一線程獲取鎖(會涉及到CAS操作,耗時)的代價而引入偏向鎖。偏向鎖的核心思想是,如果一個線程獲得鎖,那麼鎖進入偏向模式,此時Mark Word 的結構也變爲偏向鎖結構,當這個線程再次請求鎖時,無需再做任何同步操作,即獲取鎖的過程,這樣就省去了大量有關鎖申請的操作,從而也就提供程序的性能。所以,對於沒有鎖競爭的場合,偏向鎖有很好的優化效果,畢竟極有可能連續多次是同一個線程申請相同的鎖。但是對於鎖競爭比較激烈的場合,偏向鎖就失效了,因爲這樣場合極有可能每次申請鎖的線程都是不相同的,因此這種場合下不應該使用偏向鎖,否則會得不償失,需要注意的是,偏向鎖失敗後,並不會立即膨脹爲重量級鎖,而是先升級爲輕量級鎖。

輕量級鎖

若偏向鎖失敗,虛擬機不會立馬升級爲重量級鎖,它還會嘗試使用一種輕量級鎖的優化手段。此時Mark Word 的結構也變爲輕量級鎖的結構。輕量級鎖能夠提升程序性能的依據是“對絕大部分的鎖,在整個同步週期內都不存在競爭”,注意這是經驗數據。需要了解的是,輕量級鎖所適應的場景是線程交替執行同步塊的場合,如果存在同一時間訪問同一鎖的場合,就會導致輕量級鎖膨脹爲重量級鎖。

自旋鎖

輕量級鎖失敗後,虛擬機爲了避免線程真實地在操作系統層面掛起,還會進行一項稱爲自旋鎖的優化手段。這是基於在大多數情況下,線程持有鎖的時間都不會太長,如果直接掛起操作系統層面的線程可能會得不償失,畢竟操作系統實現線程之間的切換時需要從用戶態轉換到核心態,這個狀態之間的轉換需要相對比較長的時間,時間成本相對較高,因此自旋鎖會假設在不久將來,當前的線程可以獲得鎖,因此虛擬機會讓當前想要獲取鎖的線程做幾個空循環(這也是稱爲自旋的原因),一般不會太久,可能是50個循環或100循環,在經過若干次循環後,如果得到鎖,就順利進入臨界區。如果還不能獲得鎖,那就會將線程在操作系統層面掛起,這就是自旋鎖的優化方式,這種方式確實也是可以提升效率的。最後沒辦法也就只能升級爲重量級鎖了。

鎖消除

消除鎖是虛擬機另外一種鎖的優化,這種優化更徹底,Java虛擬機在JIT編譯時(可以簡單理解爲當某段代碼即將第一次被執行時進行編譯,又稱即時編譯),通過對運行上下文的掃描,去除不可能存在共享資源競爭的鎖,通過這種方式消除沒有必要的鎖,可以節省毫無意義的請求鎖時間,JVM會自動將其鎖消除。

synchronize的可重入性:

從互斥鎖的設計上來說,當一個線程試圖操作一個由其他線程持有的對象鎖的臨界資源時,將會處於阻塞狀態,但當一個線程再次請求自己持有對象鎖的臨界資源時,這種情況屬於重入鎖,請求將會成功,在java中synchronized是基於原子性的內部鎖機制,是可重入的,因此在一個線程調用synchronized方法的同時在其方法體內部調用該對象另一個synchronized方法,也就是說一個線程得到一個對象鎖後再次請求該對象鎖,是允許的,這就是synchronized的可重入性。

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