前置知識點:對象頭
要了解鎖優化策略中的輕量級鎖與偏向鎖的原理和運做過程,須要先了解Hotspot虛擬機的對象頭部分的內存佈局。java
對象頭(摘自《深刻理解java虛擬機》)
對象頭信息是與對象自身定義的數據無關的額外存儲成本數組
若是對象是數組類型,則虛擬機用3個Word(字寬,在32位虛擬機中,一字寬等於四字節,即32bit)存儲對象頭。若是對象是非數組類型,則用2Word存儲對象頭。一個額外的字寬用於存儲數組長度。安全
第一個字寬,用來存儲對象自身的運行時數據 如:哈希嗎(HashCode)、GC分代年齡(Generational GC Age)等,這部分數據的長度在32位和64位的虛擬機中分別爲32bit和64bit,簡稱「Mark Word」。多線程
第二個字寬用於存儲指向方法區對象類型數據的指針。佈局
對象頭信息會根據對象的狀態複用本身的存儲空間。例如:在32位的HotSpot虛擬機中對象未被鎖定的狀態下,Mark Word的32bit空間中的25bit用於存儲對象哈希嗎(HashCode),4bit用於存儲對象分代年齡,2bit用於存儲鎖標誌位,1bit固定爲0。性能
整個對象頭以下圖:第三行即爲第三部分,非數組類型的沒有第三個字寬。優化
Mark Word的默認存儲結構以下圖:spa
在運行期間Mark Word裏存儲的數據會隨着鎖標誌位的變化而變化。Mark Word可能變化爲存儲如下4種數據: 線程
在64位虛擬機下,Mark Word是64bit大小的,其存儲結構以下: 指針
鎖狀態
Java SE1.6爲了減小得到鎖和釋放鎖所帶來的性能消耗,引入了「偏向鎖」和「輕量級鎖」,
因此在Java SE1.6裏鎖一共有四種狀態,無鎖狀態,偏向鎖狀態,輕量級鎖狀態和重量級鎖狀態,它會隨着競爭狀況逐漸升級。
鎖能夠升級但不能降級,意味着偏向鎖升級成輕量級鎖後不能降級成偏向鎖。
這種鎖升級卻不能降級的策略,目的是爲了提升得到鎖和釋放鎖的效率,下文會詳細分析。
偏向鎖
Hotspot的做者通過以往的研究發現大多數狀況下鎖不只不存在多線程競爭,並且老是由同一線程屢次得到,爲了讓線程得到鎖的代價更低而引入了偏向鎖。
在沒有實際競爭的狀況下,還可以針對部分場景繼續優化。若是不只僅沒有實際競爭,自始至終,使用鎖的線程都只有一個,那麼,維護輕量級鎖都是浪費的。偏向鎖的目標是,減小無競爭且只有一個線程使用鎖的狀況下,使用輕量級鎖產生的性能消耗。輕量級鎖每次申請、釋放鎖都至少須要一次CAS,但偏向鎖只有初始化時須要一次CAS。
配置
默認-XX:+UseBiasedLocking=true
-XX:-UseBiasedLocking=false關閉偏向鎖
應用程序啓動幾秒鐘以後才激活
-XX:BiasedLockingStartupDelay = 0關閉延遲
加鎖流程
獲取偏向鎖
根據上圖,咱們能夠看到,首先判斷鎖對象是否是可偏向對象(若鎖對象已經被輕量級鎖定或者重量級鎖定了,由於鎖不會降級,因此它是不可偏向,一樣的,關閉了偏向鎖的設置-UseBiasedLocking=false,也會形成鎖對象不可偏向)
- 接下來,咱們假定鎖對象處於可偏向狀態,而且ThreadID爲0即biasable & unbiased狀態(這裏不討論epoch和age)
- 當一個線程試圖鎖住一個處於biasable & unbiased狀態的對象時,經過一個CAS將本身的ThreadID放置到Mark Word中相應的位置,若是CAS操做成功進入第(3)步不然進入(4)步
- 當進入到這一步時表明當前沒有鎖競爭,鎖對象繼續保持biasable可偏向狀態,可是這時ThreadID字段被設置成了偏向鎖全部者的ID,而後進入到第(6)步
- 當前線程執行CAS獲取偏向鎖失敗(這一步是偏向鎖的關鍵),表示在該鎖對象上存在競爭而且這個時候另一個線程在持有偏向鎖全部權。這個時候,就要判斷持有偏向鎖的線程是否還活着,由於一個線程執行完同步代碼塊後,不會主動釋放偏向鎖。若是持有偏向鎖的線程還活着,將偏向鎖消除,膨脹爲輕量級鎖,不然,將偏向鎖消除,讓爭鎖的線程持有偏向鎖。
具體過程是:當到達全局安全點(safepoint,在這個時間點上沒有字節碼正在執行)時擁有偏向鎖的線程被掛起,而後檢查持有偏向鎖的線程是否活着,若是線程不處於活動狀態,則將對象頭設置成無鎖狀態,若是線程仍然活着,鎖對象有可能升級爲輕量級鎖狀態(鎖標誌位置爲00),阻塞在安全點的原持有線程被釋放,進入到輕量級鎖的執行路徑中,繼續往下執行同步代碼。
- 當一個線程試圖鎖住一個處於biasable & biased而且ThreadID不等於本身的ID時,這時因爲存在鎖競爭必須進入到第(4)步來撤銷偏向鎖。
- 運行同步代碼塊
輕量級鎖
輕量級鎖是由偏向所升級來的,偏向鎖運行在一個線程進入同步塊的狀況下,當第二個線程加入鎖爭用的時候,偏向鎖就會升級爲輕量級鎖;
加鎖過程
- 在代碼進入同步塊的時候,若是此同步對象沒有被鎖定(鎖標誌位爲「01」狀態),虛擬機首先將在當前線程的棧幀中創建一個名爲鎖記錄(Lock Record)的空間,用於存儲鎖記錄目前的Mark Word的拷貝(稱爲Displaced Mark Word)以及記錄鎖對象的指針owner。
- 當一個線程來獲取這個鎖,虛擬機將使用CAS操做嘗試將鎖對象的Mark Word更新爲指向Lock Record的指針。若是這個更新動做成功了,那麼這個線程就擁有了該對象的鎖,而且對象Mark Word的鎖標誌位(Mark Word的最後2bit)將轉變爲「00」,即表示此對象處於輕量級鎖定狀態,這時候線程堆棧與對象頭的狀態以下:
- 若是這個更新操做失敗了,虛擬機首先會檢查對象的Mark Word是否指向當前線程的棧幀。若是指向,說明當前線程已經擁有了這個對象的鎖,那就能夠直接進入同步塊繼續執行。
不然說明這個鎖對象已經被其餘線程搶佔了。那麼它就會自旋等待鎖,必定次數後仍未得到鎖對象,則修改mark word,將其修改成重量級鎖的指針,表示該鎖對象進入了重量鎖狀態。若是有兩條以上的線程爭用同一個鎖,那輕量級鎖就再也不有效,要膨脹爲重量級鎖,鎖標誌的狀態值變爲」10」,Mark Word中存儲的就是指向重量級(互斥量)的指針。
- 由輕量鎖切換到重量鎖,是發生在輕量鎖釋放鎖的期間的,另外一邊,持有輕量級鎖的線程,以前在獲取鎖的時候它拷貝了鎖對象頭的mark word,在釋放鎖的時候若是它發如今它持有鎖的期間有其餘線程來嘗試獲取鎖了,而且該線程對mark word作了修改,二者比對發現不一致致使釋放鎖的CAS失敗,因而也切換到重量鎖,釋放輕量鎖,並喚醒阻塞的線程。