您好,登錄后才能下訂單哦!
這篇文章主要介紹了jvm細節探索之synchronized的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
在C程序代碼中我們可以利用操作系統提供的互斥鎖來實現同步塊的互斥訪問及線程的阻塞及喚醒等工作。然而在Java中除了提供LockAPI外還在語法層面上提供了synchronized關鍵字來實現互斥同步原語。那么到底在JVM內部是怎么實現synchronized關鍵子的呢?
一、synchronized的字節碼表示:
在java語言中存在兩種內建的synchronized語法:1、synchronized語句;2、synchronized方法。對于synchronized語句當Java源代碼被javac編譯成bytecode的時候,會在同步塊的入口位置和退出位置分別插入monitorenter和monitorexit字節碼指令。而synchronized方法則會被翻譯成普通的方法調用和返回指令如:invokevirtual、areturn指令,在VM字節碼層面并沒有任何特別的指令來實現被synchronized修飾的方法,而是在Class文件的方法表中將該方法的access_flags字段中的synchronized標志位置1,表示該方法是同步方法并使用調用該方法的對象或該方法所屬的Class在JVM的內部對象表示Klass做為鎖對象。
二、JVM中鎖的優化:
簡單來說在JVM中monitorenter和monitorexit字節碼依賴于底層的操作系統的MutexLock來實現的,但是由于使用MutexLock需要將當前線程掛起并從用戶態切換到內核態來執行,這種切換的代價是非常昂貴的;然而在現實中的大部分情況下,同步方法是運行在單線程環境(無鎖競爭環境)如果每次都調用MutexLock那么將嚴重的影響程序的性能。不過在jdk1.6中對鎖的實現引入了大量的優化,如鎖粗化(LockCoarsening)、鎖消除(LockElimination)、輕量級鎖(LightweightLocking)、偏向鎖(BiasedLocking)、適應性自旋(AdaptiveSpinning)等技術來減少鎖操作的開銷。
鎖粗化(LockCoarsening):也就是減少不必要的緊連在一起的unlock,lock操作,將多個連續的鎖擴展成一個范圍更大的鎖。
鎖消除(LockElimination):通過運行時JIT編譯器的逃逸分析來消除一些沒有在當前同步塊以外被其他線程共享的數據的鎖保護,通過逃逸分析也可以在線程本地Stack上進行對象空間的分配(同時還可以減少Heap上的垃圾收集開銷)。
輕量級鎖(LightweightLocking):這種鎖實現的背后基于這樣一種假設,即在真實的情況下我們程序中的大部分同步代碼一般都處于無鎖競爭狀態(即單線程執行環境),在無鎖競爭的情況下完全可以避免調用操作系統層面的重量級互斥鎖,取而代之的是在monitorenter和monitorexit中只需要依靠一條CAS原子指令就可以完成鎖的獲取及釋放。當存在鎖競爭的情況下,執行CAS指令失敗的線程將調用操作系統互斥鎖進入到阻塞狀態,當鎖被釋放的時候被喚醒(具體處理步驟下面詳細討論)。
偏向鎖(BiasedLocking):是為了在無鎖競爭的情況下避免在鎖獲取過程中執行不必要的CAS原子指令,因為CAS原子指令雖然相對于重量級鎖來說開銷比較小但還是存在非常可觀的本地延遲(可參考這篇文章)。
適應性自旋(AdaptiveSpinning):當線程在獲取輕量級鎖的過程中執行CAS操作失敗時,在進入與monitor相關聯的操作系統重量級鎖(mutexsemaphore)前會進入忙等待(Spinning)然后再次嘗試,當嘗試一定的次數后如果仍然沒有成功則調用與該monitor關聯的semaphore(即互斥鎖)進入到阻塞狀態。
三、對象頭(ObjectHeader):
在JVM中創建對象時會在對象前面加上兩個字大小的對象頭,在32位機器上一個字為32bit,根據不同的狀態位MarkWorld中存放不同的內容,如上圖所示在輕量級鎖中,MarkWord被分成兩部分,剛開始時LockWord為被設置為HashCode、最低三位表示LockWord所處的狀態,初始狀態為001表示無鎖狀態。Klassptr指向Class字節碼在虛擬機內部的對象表示的地址。Fields表示連續的對象實例字段。
四、MonitorRecord:
MonitorRecord是線程私有的數據結構,每一個線程都有一個可用monitorrecord列表,同時還有一個全局的可用列表;那么這些monitorrecord有什么用呢?每一個被鎖住的對象都會和一個monitorrecord關聯(對象頭中的LockWord指向monitorrecord的起始地址,由于這個地址是8byte對齊的所以LockWord的最低三位可以用來作為狀態位),同時monitorrecord中有一個Owner字段存放擁有該鎖的線程的唯一標識,表示該鎖被這個線程占用。如下圖所示為MonitorRecord的內部結構:
Owner:初始時為NULL表示當前沒有任何線程擁有該monitor record,當線程成功擁有該鎖后保存線程唯一標識,當鎖被釋放時又設置為NULL;
EntryQ:關聯一個系統互斥鎖(semaphore),阻塞所有試圖鎖住monitor record失敗的線程。
RcThis:表示blocked或waiting在該monitor record上的所有線程的個數。
Nest:用來實現重入鎖的計數。
HashCode:保存從對象頭拷貝過來的HashCode值(可能還包含GC age)。
Candidate:用來避免不必要的阻塞或等待線程喚醒,因為每一次只有一個線程能夠成功擁有鎖,如果每次前一個釋放鎖的線程喚醒所有正在阻塞或等待的線程,會引起不必要的上下文切換(從阻塞到就緒然后因為競爭鎖失敗又被阻塞)從而導致性能嚴重下降。Candidate只有兩種可能的值0表示沒有需要喚醒的線程1表示要喚醒一個繼任線程來競爭鎖。
五、輕量級鎖具體實現:
一個線程能夠通過兩種方式鎖住一個對象:1、通過膨脹一個處于無鎖狀態(狀態位001)的對象獲得該對象的鎖;2、對象已經處于膨脹狀態(狀態位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列表。
感謝你能夠認真閱讀完這篇文章,希望小編分享的“jvm細節探索之synchronized的示例分析”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。