JVM內部細節之一:synchronized關鍵字及實現細節(輕量級鎖Lightweight Locking)

  在C程序代碼中咱們能夠利用操做系統提供的互斥鎖來實現同步塊的互斥訪問及線程的阻塞及喚醒等工做。然而在Java中除了提供Lock API外還在語法層面上提供了synchronized關鍵字來實現互斥同步原語。那麼到底在JVM內部是怎麼實現synchronized關鍵子的呢?html

 

1、synchronized的字節碼錶示:
java

      在java語言中存在兩種內建的synchronized語法:一、synchronized語句;二、synchronized方法。對於synchronized語句當Java源代碼被javac編譯成bytecode的時候,會在同步塊的入口位置和退出位置分別插入monitorenter和monitorexit字節碼指令。而synchronized方法則會被翻譯成普通的方法調用和返回指令如:invokevirtual、areturn指令,在VM字節碼層面並無任何特別的指令來實現被synchronized修飾的方法,而是在Class文件的方法表中將該方法的access_flags字段中的synchronized標誌位置1,表示該方法是同步方法並使用調用該方法的對象或該方法所屬的Class在JVM的內部對象表示Klass作爲鎖對象。數據結構

 

2、JVM中鎖的優化:多線程

      簡單來講在JVM中monitorenter和monitorexit字節碼依賴於底層的操做系統的Mutex Lock來實現的,可是因爲使用Mutex Lock須要將當前線程掛起並從用戶態切換到內核態來執行,這種切換的代價是很是昂貴的;然而在現實中的大部分狀況下,同步方法是運行在單線程環境(無鎖競爭環境)若是每次都調用Mutex Lock那麼將嚴重的影響程序的性能。不過在jdk1.6中對鎖的實現引入了大量的優化,如鎖粗化(Lock Coarsening)、鎖消除(Lock Elimination)、輕量級鎖(Lightweight Locking)、偏向鎖(Biased Locking)、適應性自旋(Adaptive Spinning)等技術來減小鎖操做的開銷。oracle

鎖粗化(Lock Coarsening):也就是減小沒必要要的緊連在一塊兒的unlock,lock操做,將多個連續的鎖擴展成一個範圍更大的鎖。jvm

鎖消除(Lock Elimination):經過運行時JIT編譯器的逃逸分析來消除一些沒有在當前同步塊之外被其餘線程共享的數據的鎖保護,經過逃逸分析也能夠在線程本地Stack上進行對象空間的分配(同時還能夠減小Heap上的垃圾收集開銷)。oop

輕量級鎖(Lightweight Locking):這種鎖實現的背後基於這樣一種假設,即在真實的狀況下咱們程序中的大部分同步代碼通常都處於無鎖競爭狀態(即單線程執行環境),在無鎖競爭的狀況下徹底能夠避免調用操做系統層面的重量級互斥鎖,取而代之的是在monitorenter和monitorexit中只須要依靠一條CAS原子指令就能夠完成鎖的獲取及釋放。當存在鎖競爭的狀況下,執行CAS指令失敗的線程將調用操做系統互斥鎖進入到阻塞狀態,當鎖被釋放的時候被喚醒(具體處理步驟下面詳細討論)。性能

偏向鎖(Biased Locking):是爲了在無鎖競爭的狀況下避免在鎖獲取過程當中執行沒必要要的CAS原子指令,由於CAS原子指令雖然相對於重量級鎖來講開銷比較小但仍是存在很是可觀的本地延遲(可參考這篇文章)。優化

適應性自旋(Adaptive Spinning):當線程在獲取輕量級鎖的過程當中執行CAS操做失敗時,在進入與monitor相關聯的操做系統重量級鎖(mutex semaphore)前會進入忙等待(Spinning)而後再次嘗試,當嘗試必定的次數後若是仍然沒有成功則調用與該monitor關聯的semaphore(即互斥鎖)進入到阻塞狀態。spa

 

3、對象頭(Object Header):

 

在JVM中建立對象時會在對象前面加上兩個字大小的對象頭,在32位機器上一個字爲32bit,根據不一樣的狀態位Mark World中存放不一樣的內容,如上圖所示在輕量級鎖中,Mark Word被分紅兩部分,剛開始時LockWord爲被設置爲HashCode、最低三位表示LockWord所處的狀態,初始狀態爲001表示無鎖狀態。Klass ptr指向Class字節碼在虛擬機內部的對象表示的地址。Fields表示連續的對象實例字段。

 

4、Monitor Record:

   Monitor Record是線程私有的數據結構,每個線程都有一個可用monitor record列表,同時還有一個全局的可用列表;那麼這些monitor record有什麼用呢?每個被鎖住的對象都會和一個monitor record關聯(對象頭中的LockWord指向monitor record的起始地址,因爲這個地址是8byte對齊的因此LockWord的最低三位能夠用來做爲狀態位),同時monitor record中有一個Owner字段存放擁有該鎖的線程的惟一標識,表示該鎖被這個線程佔用。以下圖所示爲Monitor Record的內部結構:

 

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

EntryQ:關聯一個系統互斥鎖(semaphore),阻塞全部試圖鎖住monitor record失敗的線程。

RcThis:表示blocked或waiting在該monitor record上的全部線程的個數。

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

HashCode:保存從對象頭拷貝過來的HashCode值(可能還包含GC age)。

Candidate:用來避免沒必要要的阻塞或等待線程喚醒,由於每一次只有一個線程可以成功擁有鎖,若是每次前一個釋放鎖的線程喚醒全部正在阻塞或等待的線程,會引發沒必要要的上下文切換(從阻塞到就緒而後由於競爭鎖失敗又被阻塞)從而致使性能嚴重降低。Candidate只有兩種可能的值0表示沒有須要喚醒的線程1表示要喚醒一個繼任線程來競爭鎖。

 

5、輕量級鎖具體實現:

     一個線程可以經過兩種方式鎖住一個對象:一、經過膨脹一個處於無鎖狀態(狀態位001)的對象得到該對象的鎖;二、對象已經處於膨脹狀態(狀態位00)但LockWord指向的monitor record的Owner字段爲NULL,則能夠直接經過CAS原子指令嘗試將Owner設置爲本身的標識來得到鎖。

獲取鎖(monitorenter)的大概過程以下:

(1)當對象處於無鎖狀態時(RecordWord值爲HashCode,狀態位爲001),線程首先從本身的可用moniter record列表中取得一個空閒的moniter record,初始Nest和Owner值分別被預先設置爲1和該線程本身的標識,一旦monitor record準備好而後咱們經過CAS原子指令安裝該monitor record的起始地址到對象頭的LockWord字段來膨脹(原文爲inflate,我以爲之因此叫inflate主要是因爲當對象被膨脹後擴展了對象的大小;爲了空間效率,將monitor record結構從對象頭中抽出去,當須要的時候纔將該結構attach到對象上,可是和這篇Paper有點互相矛盾,兩種實現方式稍微有點不一樣)該對象,若是存在其餘線程競爭鎖的狀況而調用CAS失敗,則只須要簡單的回到monitorenter從新開始獲取鎖的過程便可。

(2)對象已經被膨脹同時Owner中保存的線程標識爲獲取鎖的線程本身,這就是重入(reentrant)鎖的狀況,只須要簡單的將Nest加1便可。不須要任何原子操做,效率很是高。

(3)對象已膨脹但Owner的值爲NULL,當一個鎖上存在阻塞或等待的線程同時鎖的前一個擁有者剛釋放鎖時會出現這種狀態,此時多個線程經過CAS原子指令在多線程競爭狀態下試圖將Owner設置爲本身的標識來得到鎖,競爭失敗的線程在則會進入到第四種狀況(4)的執行路徑。

(4)對象處於膨脹狀態同時Owner不爲NULL(被鎖住),在調用操做系統的重量級的互斥鎖以前先自旋必定的次數,當達到必定的次數時若是仍然沒有成功得到鎖,則開始準備進入阻塞狀態,首先將rfThis的值原子性的加1,因爲在加1的過程當中可能會被其餘線程破壞Object和monitor record之間的關聯,因此在原子性加1後須要再進行一次比較以確保LockWord的值沒有被改變,當發現被改變後則要從新進行monitorenter過程。同時再一次觀察Owner是否爲NULL,若是是則調用CAS參與競爭鎖,鎖競爭失敗則進入到阻塞狀態。

釋放鎖(monitorexit)的大概過程以下:

(1)首先檢查該對象是否處於膨脹狀態而且該線程是這個鎖的擁有者,若是發現不對則拋出異常;

(2)檢查Nest字段是否大於1,若是大於1則簡單的將Nest減1並繼續擁有鎖,若是等於1,則進入到第(3)步;

(3)檢查rfThis是否大於0,設置Owner爲NULL而後喚醒一個正在阻塞或等待的線程再一次試圖獲取鎖,若是等於0則進入到第(4)步

(4)縮小(deflate)一個對象,經過將對象的LockWord置換回原來的HashCode值來解除和monitor record之間的關聯來釋放鎖,同時將monitor record放回到線程是有的可用monitor record列表。

 

6、參考資料:

注:有理解錯誤之處歡迎指出,謝謝!

相關文章
相關標籤/搜索