一、使用背景,爲什麼要使用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