天天看點

JVM中鎖優化,偏向鎖、自旋鎖、鎖消除、鎖膨脹

本文将簡單介紹hotspot虛拟機中用到的鎖優化技術。

互斥同步對性能最大的影響是阻塞的實作,挂起線程和恢複線程的操作都需要轉入核心态中完成,這些操作給系統的并發性能帶來了很大的壓力。而在很多應用上,共享資料的鎖定狀态隻會持續很短的一段時間。若實體機上有多個處理器,能讓兩個以上的線程同時并行執行,我們就可以讓後面請求鎖的那個線程原地自旋(不放棄cpu時間),看看持有鎖的線程是否很快就會釋放鎖。為了讓線程等待,我們隻須讓線程執行一個忙循環(自旋),這項技術就是自旋鎖。

如果鎖長時間被占用,則浪費處理器資源,是以自旋等待的時間必須要有一定的限度,如果自旋超過了限定的次數仍然沒有成功獲得鎖,就應當使用傳統的方式去挂起線程了(預設10次)。

jdk1.6引入自适應的自旋鎖:自旋時間不再固定,由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀态來決定。如果在同一個鎖對象上,自旋等待剛剛成功獲得過鎖,并且持有鎖的線程正在運作中,那麼虛拟機就會認為這次自旋也很有可能再次成功,進而它将允許自旋等待持續相對更長的時間。

鎖削除是指虛拟機即時編譯器在運作時,對一些代碼上要求同步,但是被檢測到不可能存在共享資料競争的鎖進行削除(主要判定依據來源于逃逸分析的資料支援,如果判斷到一段代碼中,在堆上的所有資料都不會逃逸出去被其他線程通路到,那就可以把它們當作棧上資料對待,認為它們是線程私有的,同步加鎖自然就無須進行)。

如果一系列的連續操作都對同一個對象反複加鎖和解鎖,甚至加鎖操作是出現在循環體中的,那即使沒有線程競争,頻繁地進行互斥同步操作也會導緻不必要的性能損耗。 如果虛拟機探測到有這樣一串零碎的操作都對同一個對象加鎖,将會把加鎖同步的範圍擴充(膨脹)到整個操作序列的外部(由多次加鎖程式設計隻加鎖一次)。

輕量級鎖并不是用來代替重量級鎖(傳統鎖機制,如互斥等)的,目的是在沒有多線程競争的前提下,減少傳統的重量級鎖使用作業系統互斥量産生的性能消耗。

hotspot虛拟機的對象頭(object header)分為兩部分資訊,第一部分用于存儲對象自身的運作時資料,如哈希碼(hashcode)、gc分代年齡(generational gc age)等,這部分資料的長度在32位和64位的虛拟機中分别為32個和64個bits,官方稱它為“mark word”,它是實作輕量級鎖和偏向鎖的關鍵。另外一部分用于存儲指向方法區對象類型資料的指針,如果是數組對象的話,還會有一個額外的部分用于存儲數組長度。

mark word被設計成一個非固定的資料結構以便在極小的空間記憶體儲盡量多的資訊,它會根據對象的狀态複用自己的存儲空間。例如在32位的hotspot虛拟機中對象未被鎖定的狀态下,mark word的32個bits空間中的25bits用于存儲對象哈希碼(hashcode),4bits用于存儲對象分代年齡,2bits用于存儲鎖标志位,1bit固定為0,在其他狀态(輕量級鎖定、重量級鎖定、gc标記、可偏向)下mark word的存儲内容如下表所示。

存儲内容

标志位

狀态

對象hash值、對象分代年齡

01

未鎖定

指向鎖記錄的指針

00

輕量級鎖定

指向重量級鎖的指針

10

膨脹(重量級鎖定)

空,不記錄資訊

11

gc标記

偏向線程id、偏向時間戳、對象分代年齡

可偏向

在代碼進入同步塊的時候,如果此同步對象沒有被鎖定(鎖标志位為“01”狀态),虛拟機首先将在目前線程的棧幀中建立一個名為鎖記錄(lock record)的空間,用于存儲鎖對象目前的mark word的拷貝(官方把這份拷貝加了一個displaced字首,即displaced mark word),這時候線程堆棧與對象頭的狀态如下圖所示。

JVM中鎖優化,偏向鎖、自旋鎖、鎖消除、鎖膨脹

然後,虛拟機将使用cas操作嘗試将對象的mark word更新為指向lock record的指針。如果這個更新動作成功,那麼這個線程就擁有了該對象的鎖,并且對象mark word的鎖标志位(mark word的最後兩個bits)将轉變為“00”,即表示此對象處于輕量級鎖定狀态,這時候線程堆棧與對象頭的狀态如下圖所示。

JVM中鎖優化,偏向鎖、自旋鎖、鎖消除、鎖膨脹

如果這個更新操作失敗了,虛拟機首先會檢查對象的mark word是否指向目前線程的棧幀,如果是就說明目前線程已經擁有了這個對象的鎖,那就可以直接進入同步塊繼續執行,否則說明這個鎖對象已經被其他線程搶占了。如果有兩條以上的線程争用同一個鎖,那輕量級鎖就不再有效,要膨脹為重量級鎖,鎖标志的狀态值變為“10”,mark word中存儲的就是指向重量級鎖(互斥量)的指針,後面等待鎖的線程也要進入阻塞狀态。

解鎖過程也是通過cas操作來進行的,如果對象的mark word仍然指向着線程的鎖記錄,那就用cas操作把對象目前的mark word和線程中複制的displaced mark word替換回來,如果替換成功,整個同步過程就完成了。如果替換失敗,說明有其他線程嘗試過擷取該鎖,那就要在釋放鎖的同時,喚醒被挂起的線程。

輕量級鎖能提升程式同步性能的依據是“對于絕大部分的鎖,在整個同步周期内都是不存在競争的”,這是一個經驗資料。如果沒有競争,輕量級鎖使用cas操作避免了使用互斥量的開銷,但如果存在鎖競争,除了互斥量的開銷外,還額外發生了cas操作,是以在有競争的情況下,輕量級鎖會比傳統的重量級鎖更慢。

目的是消除資料在無競争情況下的同步原語,進一步提高程式的運作性能。如果說輕量級鎖是在無競争的情況下使用cas操作去消除同步使用的互斥量,那偏向鎖就是在無競争的情況下把整個同步都消除掉,連cas操作都不做了。

偏向鎖會偏向于第一個獲得它的線程(mark word中的偏向線程id資訊),如果在接下來的執行過程中,該鎖沒有被其他的線程擷取,則持有偏向鎖的線程将永遠不需要再進行同步。

假設目前虛拟機啟用了偏向鎖(啟用參數-xx:+usebiasedlocking,jdk 1.6的預設值),當鎖對象第一次被線程擷取的時候,虛拟機将會把對象頭中的标志位設為“01”,即偏向模式。同時使用cas操作把擷取到這個鎖的線程的id記錄在對象的mark word之中,如果cas操作成功,持有偏向鎖的線程以後每次進入這個鎖相關的同步塊時,虛拟機都可以不再進行任何同步操作(例如locking、unlocking及對mark word的update等)。    當有另外一個線程去嘗試擷取這個鎖時,偏向模式就宣告結束。根據鎖對象目前是否處于被鎖定的狀态,撤銷偏向(revoke bias)後恢複到未鎖定(标志位為“01”)或輕量級鎖定(标志位為“00”)的狀态,後續的同步操作就如上面介紹的輕量級鎖那樣執行。偏向鎖、輕量級鎖的狀态轉化及對象mark word的關系如下圖所示。

JVM中鎖優化,偏向鎖、自旋鎖、鎖消除、鎖膨脹

偏向鎖可以提高帶有同步但無競争的程式性能。它同樣是一個帶有效益權衡(trade off)性質的優化,也就是說它并不一定總是對程式運作有利,如果程式中大多數的鎖都總是被多個不同的線程通路,那偏向模式就是多餘的。

特别說明:尊重作者的勞動成果,轉載請注明出處哦~~~http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt364