上一篇經過構建金字塔結構,來從不一樣的角度,由淺入深的對synchronized關鍵字作了介紹,html
快速跳轉:http://www.javashuo.com/article/p-yxisdqpw-ba.html程序員
本文將從底層實現的各個「組件」着手,詳細拆解其工做原理。數組
本文會分爲如下4節內容:安全
第一節:介紹MarkWord和LockRecord兩種數據結構,該知識點是理解synchronized關鍵字底層原理的關鍵。數據結構
第二節:分析偏向鎖加鎖解鎖時機和過程多線程
在HotSpot虛擬機中,Java對象在內存中存儲的佈局,分爲三個部分:對象頭,實例數據,對齊填充。佈局
本文重點關注對象頭。spa
對象頭又劃分爲2或3部分,具體包括:線程
本文重點關注MW區域設計
MW是一塊固定大小內存區域,在32位虛擬機中是32個bit,對應的,64位虛擬機中是64個bit。本文以32位虛擬機爲例分析。
咱們從直觀上理解,所謂的頭信息,通常都是用來記載一些不易變的信息,例如在http請求頭中的各類頭信息。在對象頭中也是如此,例如hashcode。在JVM虛擬機中爲了解決存儲空間開銷,對象頭的MW大小已經固定。那麼,要存儲的信息有比較多,包括且不限於:鎖標誌位、GC信息、鎖相關信息,總大小遠遠超出32bit,怎麼辦呢?
共享存儲區域,在不一樣的時刻,根據需求存儲須要的信息。
請參考下圖:
鎖類型 |
25bit |
4bit |
1bit |
2bit |
|
---|---|---|---|---|---|
23bit |
2bit |
是否偏向鎖 |
鎖標誌位 |
||
無鎖 |
對象hashcode |
分代年齡 |
0 |
01 |
|
偏向鎖 |
線程ID |
epoch |
分代年齡 |
1 |
01 |
輕量級鎖 |
指向棧中鎖記錄的指針 |
00 |
|||
重量級鎖 |
指向互斥量 |
10 |
|||
GC標記 |
空 |
11 |
說明:兩個標誌位最多隻能標識4個狀態,那麼剩下一個怎麼辦?共享。無鎖和偏向鎖共享01狀態,他們兩個的區分
在當前線程的棧中申請LR(LockRecord簡稱,下同),主要包含兩部分,第一步部分能夠用於存放MW的副本;第二部分obj,用於指向鎖對象。
上述二者的關係用下圖表示:
在對象建立的時候,MW會有一個初始態,要麼是無鎖態,要麼是初始偏向鎖態(ThreadId、epoch值都爲初始值0)。程序員的世界不存在二義性,最終總會選一個,選擇的依據是虛擬機的配置參數,在JDK1.6之後,默認是開啓的,若是要禁用掉:-XX:-UseBiasedLocking。
何時須要禁用呢?若是能確認程序在大多數狀況下,都存在多線程競爭,那麼就能夠禁用掉偏向鎖。不必每次都走一遍偏向鎖->輕量級鎖->重量級鎖的完整升級流程。
1.先放一張圖,直觀的描述偏向鎖的加鎖、解鎖、撤銷基本流程
2.加鎖過程
步驟一:
步驟二:如圖中所示,線程T1,執行到同步代碼,嘗試加偏向鎖,首先會作【偏向鎖是否可用】的判斷:
可加鎖狀態的MW內容以下圖所示:
鎖類型 |
25bit |
|
4bit |
1bit |
2bit |
|
23bit |
2bit |
|
是否偏向鎖 |
鎖標誌位 |
偏向鎖 |
ThreadId==0 |
epoch==n |
分代年齡 |
1 |
01 |
以上三個點都判斷經過,進入「第二步」,加鎖流程
第二步:經過CAS原子操做,把T1的ThreadId寫入MW。執行結果有兩種狀況:
2.解鎖過程
當前線程執行完同步代碼塊後,進行解鎖,解鎖操做比較簡單,僅僅將棧中的最近一條LR中的obj賦值爲null。這裏須要注意,MW中的threadId並不會作修改。
3.鎖競爭處理流程
持有鎖的線程T2並不會在發現競爭的第一時間就直接撤銷鎖,或者升級鎖,而是執行到安全點後再處理。
ps:怎麼判斷是否還在執行同步代碼呢?遍歷棧中的RL,若是都爲null,表明鎖已所有釋放。
4.批量重偏向和批量撤銷
有這樣一種場景:若是咱們預判競爭很少,大部分狀況下是單一線程執行同步塊,開啓了偏向鎖。可是在實際使用環境中,出現了大量的競爭,這時候怎麼辦呢?停機從新配置參數?恐怕不是最好的方案。若是是咱們來設計這個這個Synchronized鎖,確定也會作一些兜底策略。好比這樣來作,當某一事件發生了N次,那麼就更改一下處理策略?
是的,基本思想差很少,只不過更完善,暫時留一個懸念,在下次揭曉。