JVM中鎖優化簡介

本文將簡單介紹HotSpot虛擬機中用到的鎖優化技術。 html

自旋鎖

互斥同步對性能最大的影響是阻塞的實現,掛起線程和恢復線程的操做都須要轉入內核態中完成,這些操做給系統的併發性能帶來了很大的壓力。而在不少應用上,共享數據的鎖定狀態只會持續很短的一段時間。若實體機上有多個處理器,能讓兩個以上的線程同時並行執行,咱們就可讓後面請求鎖的那個線程原地自旋(不放棄CPU時間),看看持有鎖的線程是否很快就會釋放鎖。爲了讓線程等待,咱們只須讓線程執行一個忙循環(自旋),這項技術就是自旋鎖。 java

若是鎖長時間被佔用,則浪費處理器資源,所以自旋等待的時間必需要有必定的限度,若是自旋超過了限定的次數仍然沒有成功得到鎖,就應當使用傳統的方式去掛起線程了(默認10次)。 編程

JDK1.6引入自適應的自旋鎖:自旋時間再也不固定,由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態來決定。若是在同一個鎖對象上,自旋等待剛剛成功得到過鎖,而且持有鎖的線程正在運行中,那麼虛擬機就會認爲此次自旋也頗有可能再次成功,進而它將容許自旋等待持續相對更長的時間。 數組

鎖削除

鎖削除是指虛擬機即時編譯器在運行時,對一些代碼上要求同步,可是被檢測到不可能存在共享數據競爭的鎖進行削除(主要斷定依據來源於逃逸分析的數據支持,若是判斷到一段代碼中,在堆上的全部數據都不會逃逸出去被其餘線程訪問到,那就能夠把它們看成棧上數據對待,認爲它們是線程私有的,同步加鎖天然就無須進行)。 數據結構

鎖膨脹

若是一系列的連續操做都對同一個對象反覆加鎖和解鎖,甚至加鎖操做是出如今循環體中的,那即便沒有線程競爭,頻繁地進行互斥同步操做也會致使沒必要要的性能損耗。 若是虛擬機探測到有這樣一串零碎的操做都對同一個對象加鎖,將會把加鎖同步的範圍擴展(膨脹)到整個操做序列的外部(由屢次加鎖編程只加鎖一次)。 多線程

輕量級鎖

輕量級鎖並非用來代替重量級鎖(傳統鎖機制,如互斥等)的,目的是在沒有多線程競爭的前提下,減小傳統的重量級鎖使用操做系統互斥量產生的性能消耗。 併發

HotSpot虛擬機的對象頭(Object Header)分爲兩部分信息,第一部分用於存儲對象自身的運行時數據,如哈希碼(HashCode)、GC分代年齡(Generational GC Age)等,這部分數據的長度在32位和64位的虛擬機中分別爲32個和64個Bits,官方稱它爲「Mark Word」,它是實現輕量級鎖和偏向鎖的關鍵。另一部分用於存儲指向方法區對象類型數據的指針,若是是數組對象的話,還會有一個額外的部分用於存儲數組長度。 oracle

Mark Word被設計成一個非固定的數據結構以便在極小的空間內存儲儘可能多的信息,它會根據對象的狀態複用本身的存儲空間。例如在32位的HotSpot虛擬機中對象未被鎖定的狀態下,Mark Word的32個Bits空間中的25Bits用於存儲對象哈希碼(HashCode),4Bits用於存儲對象分代年齡,2Bits用於存儲鎖標誌位,1Bit固定爲0,在其餘狀態(輕量級鎖定、重量級鎖定、GC標記、可偏向)下Mark Word的存儲內容以下表所示。 jvm

存儲內容 標誌位 狀態
對象Hash值、對象分代年齡 01 未鎖定
指向鎖記錄的指針 00 輕量級鎖定
指向重量級鎖的指針 10 膨脹(重量級鎖定)
空,不記錄信息 11 GC標記
偏向線程ID、偏向時間戳、對象分代年齡 01 可偏向

加鎖過程 oop

在代碼進入同步塊的時候,若是此同步對象沒有被鎖定(鎖標誌位爲「01」狀態),虛擬機首先將在當前線程的棧幀中創建一個名爲鎖記錄(Lock Record)的空間,用於存儲鎖對象目前的Mark Word的拷貝(官方把這份拷貝加了一個Displaced前綴,即Displaced Mark Word),這時候線程堆棧與對象頭的狀態以下圖所示。

而後,虛擬機將使用CAS操做嘗試將對象的Mark Word更新爲指向Lock Record的指針。若是這個更新動做成功,那麼這個線程就擁有了該對象的鎖,而且對象Mark Word的鎖標誌位(Mark Word的最後兩個Bits)將轉變爲「00」,即表示此對象處於輕量級鎖定狀態,這時候線程堆棧與對象頭的狀態以下圖所示。

若是這個更新操做失敗了,虛擬機首先會檢查對象的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的關係以下圖所示。

偏向鎖能夠提升帶有同步但無競爭的程序性能。它一樣是一個帶有效益權衡(Trade Off)性質的優化,也就是說它並不必定老是對程序運行有利,若是程序中大多數的鎖都老是被多個不一樣的線程訪問,那偏向模式就是多餘的。

主要參考

  1. 《深刻理解Java虛擬機:JVM高級特性與最佳實踐》
  2. The Hotspot Java Virtual Machine
  3. Eliminating Synchronization-Related Atomic Operations with Biased Locking and Bulk Rebiasing
相關文章
相關標籤/搜索