這篇文章主要介紹了jvm細節(jié)探索之synchronized的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
十載的黃平網(wǎng)站建設(shè)經(jīng)驗,針對設(shè)計、前端、開發(fā)、售后、文案、推廣等六對一服務(wù),響應(yīng)快,48小時及時工作處理。全網(wǎng)整合營銷推廣的優(yōu)勢是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動調(diào)整黃平建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計,從而大程度地提升瀏覽體驗。創(chuàng)新互聯(lián)公司從事“黃平網(wǎng)站設(shè)計”,“黃平網(wǎng)站推廣”以來,每個客戶項目都認真落實執(zhí)行。在C程序代碼中我們可以利用操作系統(tǒng)提供的互斥鎖來實現(xiàn)同步塊的互斥訪問及線程的阻塞及喚醒等工作。然而在Java中除了提供LockAPI外還在語法層面上提供了synchronized關(guān)鍵字來實現(xiàn)互斥同步原語。那么到底在JVM內(nèi)部是怎么實現(xiàn)synchronized關(guān)鍵子的呢?
一、synchronized的字節(jié)碼表示:
在java語言中存在兩種內(nèi)建的synchronized語法:1、synchronized語句;2、synchronized方法。對于synchronized語句當Java源代碼被javac編譯成bytecode的時候,會在同步塊的入口位置和退出位置分別插入monitorenter和monitorexit字節(jié)碼指令。而synchronized方法則會被翻譯成普通的方法調(diào)用和返回指令如:invokevirtual、areturn指令,在VM字節(jié)碼層面并沒有任何特別的指令來實現(xiàn)被synchronized修飾的方法,而是在Class文件的方法表中將該方法的access_flags字段中的synchronized標志位置1,表示該方法是同步方法并使用調(diào)用該方法的對象或該方法所屬的Class在JVM的內(nèi)部對象表示Klass做為鎖對象。
二、JVM中鎖的優(yōu)化:
簡單來說在JVM中monitorenter和monitorexit字節(jié)碼依賴于底層的操作系統(tǒng)的MutexLock來實現(xiàn)的,但是由于使用MutexLock需要將當前線程掛起并從用戶態(tài)切換到內(nèi)核態(tài)來執(zhí)行,這種切換的代價是非常昂貴的;然而在現(xiàn)實中的大部分情況下,同步方法是運行在單線程環(huán)境(無鎖競爭環(huán)境)如果每次都調(diào)用MutexLock那么將嚴重的影響程序的性能。不過在jdk1.6中對鎖的實現(xiàn)引入了大量的優(yōu)化,如鎖粗化(LockCoarsening)、鎖消除(LockElimination)、輕量級鎖(LightweightLocking)、偏向鎖(BiasedLocking)、適應(yīng)性自旋(AdaptiveSpinning)等技術(shù)來減少鎖操作的開銷。
鎖粗化(LockCoarsening):也就是減少不必要的緊連在一起的unlock,lock操作,將多個連續(xù)的鎖擴展成一個范圍更大的鎖。
鎖消除(LockElimination):通過運行時JIT編譯器的逃逸分析來消除一些沒有在當前同步塊以外被其他線程共享的數(shù)據(jù)的鎖保護,通過逃逸分析也可以在線程本地Stack上進行對象空間的分配(同時還可以減少Heap上的垃圾收集開銷)。
輕量級鎖(LightweightLocking):這種鎖實現(xiàn)的背后基于這樣一種假設(shè),即在真實的情況下我們程序中的大部分同步代碼一般都處于無鎖競爭狀態(tài)(即單線程執(zhí)行環(huán)境),在無鎖競爭的情況下完全可以避免調(diào)用操作系統(tǒng)層面的重量級互斥鎖,取而代之的是在monitorenter和monitorexit中只需要依靠一條CAS原子指令就可以完成鎖的獲取及釋放。當存在鎖競爭的情況下,執(zhí)行CAS指令失敗的線程將調(diào)用操作系統(tǒng)互斥鎖進入到阻塞狀態(tài),當鎖被釋放的時候被喚醒(具體處理步驟下面詳細討論)。
偏向鎖(BiasedLocking):是為了在無鎖競爭的情況下避免在鎖獲取過程中執(zhí)行不必要的CAS原子指令,因為CAS原子指令雖然相對于重量級鎖來說開銷比較小但還是存在非??捎^的本地延遲(可參考這篇文章)。
適應(yīng)性自旋(AdaptiveSpinning):當線程在獲取輕量級鎖的過程中執(zhí)行CAS操作失敗時,在進入與monitor相關(guān)聯(lián)的操作系統(tǒng)重量級鎖(mutexsemaphore)前會進入忙等待(Spinning)然后再次嘗試,當嘗試一定的次數(shù)后如果仍然沒有成功則調(diào)用與該monitor關(guān)聯(lián)的semaphore(即互斥鎖)進入到阻塞狀態(tài)。
三、對象頭(ObjectHeader):
在JVM中創(chuàng)建對象時會在對象前面加上兩個字大小的對象頭,在32位機器上一個字為32bit,根據(jù)不同的狀態(tài)位MarkWorld中存放不同的內(nèi)容,如上圖所示在輕量級鎖中,MarkWord被分成兩部分,剛開始時LockWord為被設(shè)置為HashCode、最低三位表示LockWord所處的狀態(tài),初始狀態(tài)為001表示無鎖狀態(tài)。Klassptr指向Class字節(jié)碼在虛擬機內(nèi)部的對象表示的地址。Fields表示連續(xù)的對象實例字段。
四、MonitorRecord:
MonitorRecord是線程私有的數(shù)據(jù)結(jié)構(gòu),每一個線程都有一個可用monitorrecord列表,同時還有一個全局的可用列表;那么這些monitorrecord有什么用呢?每一個被鎖住的對象都會和一個monitorrecord關(guān)聯(lián)(對象頭中的LockWord指向monitorrecord的起始地址,由于這個地址是8byte對齊的所以LockWord的最低三位可以用來作為狀態(tài)位),同時monitorrecord中有一個Owner字段存放擁有該鎖的線程的唯一標識,表示該鎖被這個線程占用。如下圖所示為MonitorRecord的內(nèi)部結(jié)構(gòu):
Owner:初始時為NULL表示當前沒有任何線程擁有該monitor record,當線程成功擁有該鎖后保存線程唯一標識,當鎖被釋放時又設(shè)置為NULL;
EntryQ:關(guān)聯(lián)一個系統(tǒng)互斥鎖(semaphore),阻塞所有試圖鎖住monitor record失敗的線程。
RcThis:表示blocked或waiting在該monitor record上的所有線程的個數(shù)。
Nest:用來實現(xiàn)重入鎖的計數(shù)。
HashCode:保存從對象頭拷貝過來的HashCode值(可能還包含GC age)。
Candidate:用來避免不必要的阻塞或等待線程喚醒,因為每一次只有一個線程能夠成功擁有鎖,如果每次前一個釋放鎖的線程喚醒所有正在阻塞或等待的線程,會引起不必要的上下文切換(從阻塞到就緒然后因為競爭鎖失敗又被阻塞)從而導(dǎo)致性能嚴重下降。Candidate只有兩種可能的值0表示沒有需要喚醒的線程1表示要喚醒一個繼任線程來競爭鎖。
五、輕量級鎖具體實現(xiàn):
一個線程能夠通過兩種方式鎖住一個對象:1、通過膨脹一個處于無鎖狀態(tài)(狀態(tài)位001)的對象獲得該對象的鎖;2、對象已經(jīng)處于膨脹狀態(tài)(狀態(tài)位00)但LockWord指向的monitor record的Owner字段為NULL,則可以直接通過CAS原子指令嘗試將Owner設(shè)置為自己的標識來獲得鎖。
獲取鎖(monitorenter)的大概過程如下:
(1)當對象處于無鎖狀態(tài)時(RecordWord值為HashCode,狀態(tài)位為001),線程首先從自己的可用moniter record列表中取得一個空閑的moniter record,初始Nest和Owner值分別被預(yù)先設(shè)置為1和該線程自己的標識,一旦monitor record準備好然后我們通過CAS原子指令安裝該monitor record的起始地址到對象頭的LockWord字段來膨脹(原文為inflate,我覺得之所以叫inflate主要是由于當對象被膨脹后擴展了對象的大小;為了空間效率,將monitor record結(jié)構(gòu)從對象頭中抽出去,當需要的時候才將該結(jié)構(gòu)attach到對象上,但是和這篇Paper有點互相矛盾,兩種實現(xiàn)方式稍微有點不同)該對象,如果存在其他線程競爭鎖的情況而調(diào)用CAS失敗,則只需要簡單的回到monitorenter重新開始獲取鎖的過程即可。
(2)對象已經(jīng)被膨脹同時Owner中保存的線程標識為獲取鎖的線程自己,這就是重入(reentrant)鎖的情況,只需要簡單的將Nest加1即可。不需要任何原子操作,效率非常高。
(3)對象已膨脹但Owner的值為NULL,當一個鎖上存在阻塞或等待的線程同時鎖的前一個擁有者剛釋放鎖時會出現(xiàn)這種狀態(tài),此時多個線程通過CAS原子指令在多線程競爭狀態(tài)下試圖將Owner設(shè)置為自己的標識來獲得鎖,競爭失敗的線程在則會進入到第四種情況(4)的執(zhí)行路徑。
(4)對象處于膨脹狀態(tài)同時Owner不為NULL(被鎖住),在調(diào)用操作系統(tǒng)的重量級的互斥鎖之前先自旋一定的次數(shù),當達到一定的次數(shù)時如果仍然沒有成功獲得鎖,則開始準備進入阻塞狀態(tài),首先將rfThis的值原子性的加1,由于在加1的過程中可能會被其他線程破壞Object和monitor record之間的關(guān)聯(lián),所以在原子性加1后需要再進行一次比較以確保LockWord的值沒有被改變,當發(fā)現(xiàn)被改變后則要重新進行monitorenter過程。同時再一次觀察Owner是否為NULL,如果是則調(diào)用CAS參與競爭鎖,鎖競爭失敗則進入到阻塞狀態(tài)。
釋放鎖(monitorexit)的大概過程如下:
(1)首先檢查該對象是否處于膨脹狀態(tài)并且該線程是這個鎖的擁有者,如果發(fā)現(xiàn)不對則拋出異常;
(2)檢查Nest字段是否大于1,如果大于1則簡單的將Nest減1并繼續(xù)擁有鎖,如果等于1,則進入到第(3)步;
(3)檢查rfThis是否大于0,設(shè)置Owner為NULL然后喚醒一個正在阻塞或等待的線程再一次試圖獲取鎖,如果等于0則進入到第(4)步
(4)縮?。╠eflate)一個對象,通過將對象的LockWord置換回原來的HashCode值來解除和monitor record之間的關(guān)聯(lián)來釋放鎖,同時將monitor record放回到線程是有的可用monitor record列表。
感謝你能夠認真閱讀完這篇文章,希望小編分享的“jvm細節(jié)探索之synchronized的示例分析”這篇文章對大家有幫助,同時也希望大家多多支持創(chuàng)新互聯(lián),關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,更多相關(guān)知識等著你來學習!
文章題目:jvm細節(jié)探索之synchronized的示例分析-創(chuàng)新互聯(lián)
URL分享:http://jinyejixie.com/article36/dsgosg.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供小程序開發(fā)、手機網(wǎng)站建設(shè)、微信小程序、網(wǎng)站收錄、網(wǎng)站排名、網(wǎng)站設(shè)計公司
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容