synchronize底層原理分析

一、使用背景,爲什麼要使用synchronize?synchronize能幹什麼?

1、Java多線程背景

線程安全是併發編程中的重要關注點,應該注意到的是,造成線程安全問題的主要誘因有兩點:

  • 一是存在共享數據(也稱臨界資源).
  • 二是存在多條線程共同操作共享數據。

因此爲了解決這個問題,我們可能需要這樣一個方案:

當存在多個線程操作共享數據時,需要保證同一時刻有且只有一個線程在操作共享數據,其他線程必須等到該線程處理完數據後再進行,這種方式有個高尚的名稱叫互斥鎖,即能達到互斥訪問目的的鎖,也就是說當一個共享數據被當前正在訪問的線程加上互斥鎖後,在同一個時刻,其他線程只能處於等待的狀態,直到當前線程處理完畢釋放該鎖。

2、對於以上問題,我們Java中的“synchronized”可以做到這一點,爲共享數據操作達到原子性、可見性、有序性而生

在 Java 中,關鍵字 synchronized可以保證在同一個時刻,只有一個線程可以執行某個方法或者某個代碼塊(主要是對方法或者代碼塊中存在共享數據的操作),同時我們還應該注意到synchronized另外一個重要的作用,synchronized可保證一個線程的變化(主要是共享數據的變化)被其他線程所看到(保證可見性,完全可以替代Volatile功能),這點確實也是很重要的。

3、synchronized的三種應用方式

  • 修飾實例方法,作用於當前實例加鎖,進入同步代碼前要獲得當前實例的鎖。
  • 修飾靜態方法,作用於當前類對象加鎖,進入同步代碼前要獲得當前類對象的鎖。
  • 修飾代碼塊,指定加鎖對象,對給定對象加鎖,進入同步代碼庫前要獲得給定對象的鎖。

二、synchronized運行原理分析

1、實例分析

編寫一個簡單的加了synchronized關鍵字的程序:

public class Test4 {
 
    private static Object LOCK = new Object();
 
    public static int main(String[] args) {
        synchronized (LOCK){
            System.out.println("Hello World");
        }
        return 1;
    }
}

  運行編譯後,查看編譯的class字節碼文件內容如下:

通過上圖字節碼執行文件的分析,可以看出鎖monitorenter和monitorexit來實現的。

monitorenter和monitorexit解釋:

每個對象都有一個monitor監視器,調用monitorenter就是嘗試獲取這個對象,成功獲取到了就將值+1,離開就將值減1。如果是線程重入,在將值+1,說明monitor對象是支持可重入的

2、對象如何記錄鎖狀態?

鎖是被記錄在對象頭當中

  • 實例變量:存放類的屬性數據信息,包括父類的屬性信息,如果是數組的實例部分還包括數組的長度,這部分內存按4字節對齊。
  • 填充數據:由於虛擬機要求對象起始地址必須是8字節的整數倍。填充數據不是必須存在的,僅僅是爲了字節對齊,這點了解即可

3、對象頭加鎖分析

重量級鎖:

最基礎的實現方式,JVM會阻塞未獲取到鎖的線程,在鎖被釋放的時候喚醒這些線程。阻塞和喚醒操作是依賴操作系統來完成的,所以需要從用戶態切換到內核態,開銷很大。並且monitor調用的是操作系統底層的互斥量(mutex),本身也有用戶態和內核態的切換,所以JVM引入了自旋的概念,減少上面說的線程切換的成本。

自旋鎖:

如果鎖被其他線程佔用的時間很短,那麼其他獲取鎖的線程只要稍微等一下就好了,沒必要進行用戶態和內核態之間的切換,等的狀態就叫自旋。

自旋會跑一些無用的CPU指令,所以會浪費處理器時間,如果鎖被其他線程佔用的時間短的話確實是合適的,如果長的話就不如使用直接阻塞了,那麼JVM怎麼知道鎖被佔用的時間到底是長還是短呢?

因爲JVM不知道鎖被佔用的時間長短,所以使用的是自適應自旋。就是線程空循環的次數時會動態調整的。
可以看出,自旋會導致不公平鎖,不一定等待時間最長的線程會最先獲取鎖。

jdk1.6後加入輕量級鎖:

輕量級鎖:

JDK1.6之後加入,它的目的並不是爲了替換前面的重量級鎖,而是在實際沒有鎖競爭的情況下,將申請互斥量這步也省掉。鎖實現的核心在與對象頭(MarkWord)的結構,對象自身會有信息表示所有被鎖住並且鎖是什麼類型。

(1)、輕量級鎖分析

如果代碼進入同步塊時,檢測到對象未鎖定,即標誌位爲01。那麼當前線程就會在自身棧幀中建立一個區域保存對象的MarkWord信息,再使用CAS的方式讓這個區域指向對象的MarkWork區域,這樣就算加上鎖了。(這樣就沒有獲取系統mutex變量,只是改了個值,但是如果有競爭的話,就要升級成重量級鎖,這樣反倒變慢了)

加鎖前VS 加鎖後:

(2)、偏向鎖:

比輕量級鎖更絕,將同步操作全部省略…設置步驟是和前面的輕量級鎖一樣的,不同的是標誌位設置的是01,即偏向模式。

不同的是同一個線程第二次進來之後,虛擬機不會再進行任何的同步操作,比如Mark Word的update。

如果有其他線程來,偏向模式就結束了,標誌位會恢復到未鎖定或者偏向鎖。所以如果鎖總是會被多個線程訪問的話,還是禁止掉偏向鎖優化比較好。

(4)、鎖優化流程如下:

https://www.bilibili.com/video/BV1Y4411a7Ld?from=search&seid=10271254194168906067

https://blog.csdn.net/zc19921215/article/details/84780335

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