- ObjectMonitor
在HotSpot中钾军,Monitor采用ObjectMonitor實現(xiàn)窒舟。
Monitor是一個同步工具嘲驾,通常被描述為一個對象淌哟。每一個Java對象都擁有一把看不見的Monitor鎖
。
Java中每個對象都擁有的監(jiān)視器用來監(jiān)測并發(fā)代碼的重入距淫,在非多線程編碼時Monitor監(jiān)視器不起作用绞绒,在synchronized范圍內,監(jiān)視器發(fā)揮作用
榕暇。
ObjectMonitor的結構
ObjectMonitor() {
_header = NULL;
_count = 0; //記錄個數(shù)
_waiters = 0,
_recursions = 0;
_object = NULL;
//指向持有ObjectMonitor對象的線程
_owner = NULL;
//處于wait狀態(tài)的線程蓬衡,會被加入到_WaitSet
_WaitSet = NULL;
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
//處于等待鎖block狀態(tài)的線程喻杈,會被加入到該列表
_EntryList = NULL ;
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
2.synchronized實現(xiàn)原理
synchronized在軟件層面依賴JVM虛擬機實現(xiàn)同步;JUC包中的Lock同步鎖在硬件層面依賴特殊的CPU指令實現(xiàn)同步狰晚。
synchronized通過Monitor來實現(xiàn)線程同步筒饰,Monitor依賴于底層的操作系統(tǒng)的Mutex Lock(互斥鎖)來實現(xiàn)的線程同步
。
依賴底層操作系統(tǒng)的Mutex Lock所實現(xiàn)的鎖稱之為重量級鎖
壁晒。
同步代碼塊的同步原理
synchronized同步代碼塊代碼生成的字節(jié)碼指令中包含monitorenter
和monitorexit
指令瓷们。
monitorenter指令
:執(zhí)行該指令時可以嘗試去獲取對象的monitor鎖。當獲得monitor時就會處于鎖定狀態(tài)秒咐,就可以去執(zhí)行同步代碼塊谬晕。
monitorexit指令
:執(zhí)行該指令會退出同步代碼塊并釋放鎖。執(zhí)行該指令的線程是monitor的占有者携取。
異常退出同步代碼塊時會釋放鎖攒钳,也會調用該指令。
同步方法的同步原理
同步方法的字節(jié)碼指令中沒有monitorenter
和monitorexit
指令雷滋,是通過方法修飾符上的ACC_SYNCHRONIZED標識符來實現(xiàn)方法的同步
不撑。
同步方法是一種隱式的方式實現(xiàn)同步,不需要字節(jié)碼指令來完成晤斩。
同步代碼塊中monitorenter
和monitorexit
指令的執(zhí)行是JVM通過調用操作系統(tǒng)的互斥語句mutex實現(xiàn)的焕檬,被阻塞的線程會被掛起、等待重新調度澳泵,會導致在用戶態(tài)和內核態(tài)切換实愚。
- Java對象的對象頭
在HotSpot虛擬機中,對象在內存中的存儲的布局可以分為3塊區(qū)域:對象頭兔辅、實例數(shù)據(jù)爆侣、對齊填充
。
Java對象頭
對象頭包括兩部分信息:Mark Word
和類型指針幢妄。
Mark Word是實現(xiàn)輕量級鎖和偏向鎖的關鍵。
類型指針:指向對象的類元數(shù)據(jù)的指針茫负。
Mark Word
:用于存儲對象自身的運行時數(shù)據(jù)蕉鸳,是實現(xiàn)輕量級鎖和偏向鎖的關鍵
。
包括哈希碼忍法、GC分代年齡潮尝、鎖狀態(tài)標志
、線程持有的鎖
饿序、偏向線程ID勉失、偏向時間戳。
是一個非固定的數(shù)據(jù)結構用以方便在小的空間存儲更多的信息原探。
會根據(jù)對象的狀態(tài)復用自己的存儲空間乱凿,就是說在運行期間Mark Work里存儲的數(shù)據(jù)會隨著鎖標志位的變化而變化
顽素。
- Lock Record --鎖記錄
Lock Record(鎖記錄):在線程的棧中創(chuàng)建,用來存儲鎖對象的Mark Word的拷貝徒蟆。
是線程私有的數(shù)據(jù)結構
胁出,每一個線程都有一個可用的Lock Record列表。
每一個被鎖住的對象都會和一個Lock Record關聯(lián)段审,對象頭中的Mark Word指向Lock Record的起始地址全蝶,
。
LockRecord的結構如下圖
Owner
:當線程成功獲得鎖后存放線程的唯一標識
寺枉。
EnterQ
:關聯(lián)一個互斥鎖抑淫,阻塞所有競爭鎖失敗的線程。
RcThis
:表示在該線程獲得的鎖上blocked或waiting的所有線程的個數(shù)姥闪。
Nest
:重入鎖的計數(shù)始苇。
HashCode
:保存從對象頭拷貝過來的HashCode值(可能包含GC age)。
Candidate
:只有兩種值:0或1甘畅,表示沒有需要喚醒的線程或表示要喚醒一個線程去競爭鎖埂蕊。
- 對synchronized實現(xiàn)機制進行的鎖優(yōu)化
依賴操作系統(tǒng)Mutex Lock所實現(xiàn)的重量級鎖在線程切換時會導致在用戶態(tài)和內核態(tài)之間切換,會導致很大的開銷疏唾。
JDK6之后蓄氧,引入了偏向鎖
和輕量級鎖
來減少鎖操作的開銷。
目前鎖一共有4種狀態(tài)槐脏,級別從低到高依次為:無鎖喉童、偏向鎖、輕量級鎖顿天、重量級鎖堂氯;鎖狀態(tài)只能升級不能降級
。
偏向鎖牌废、輕量級鎖屬于樂觀鎖咽白,重量級鎖屬于悲觀鎖。樂觀鎖CAS的實現(xiàn)同步鸟缕,獲取不到時會轉換為悲觀鎖
晶框。
無鎖
沒有對資源進行鎖定,所有線程都可以訪問并修改同一個資源懂从,但只有一個線程可以修改成功授段。
特點:修改操作在循環(huán)中進行。
CAS原理及應用即是無鎖的實現(xiàn)番甩。
偏向鎖
偏向于第一個訪問鎖的線程侵贵,如果在運行過程中,鎖沒有被其他線程訪問缘薛,持有偏向鎖的線程永遠不會觸發(fā)同步窍育。
偏向鎖就是為了解決在不存在多線程競爭時卡睦,鎖總是由同一個線程多次獲得的情況。目的是在只有一個線程執(zhí)行同步代碼塊時可以提高性能蔫骂。
加鎖過程
:
a.加鎖發(fā)生在鎖第一次被線程擁有時么翰,會使用CAS原子操作嘗試更新對象的Mark Word,將Mark Word中的偏向鎖標志位改為1辽旋,并記錄偏向線程的ID浩嫌。
之后在對象頭中的偏向線程ID指向的線程進入和退出相同鎖對象的同步代碼塊時不用通過CAS操作來更新對象頭。
撤銷和膨脹過程
:
b.另一個競爭線程想要進入同步代碼塊時补胚,訪問到Mark Word中偏向鎖標志為1码耐,確認是可偏向狀態(tài);同時檢查到對象頭中的偏向線程ID與該競爭線程不一致溶其,競爭線程嘗試進行CAS操作更新對象頭骚腥。
c.競爭線程更新對象頭失敗時,等待持有偏向鎖線程進入安全點(會導致STW)并暫停持有偏向鎖的線程瓶逃,根據(jù)偏向鎖線程是否處于同步代碼塊束铭,處于同步代碼塊時將偏向鎖升級為輕量級鎖
;已經退出同步代碼塊時厢绝,將鎖置為無鎖狀態(tài)契沫。
d.競爭線程更新對象頭成功時,直接進入同步代碼塊昔汉。
偏向鎖加鎖撤銷鎖流程圖:
適合在無鎖競爭情況下使用懈万,撤銷偏向鎖時會導致stop the world
。
問題
:為什么在到達全局安全點時才會進行偏向鎖的釋放靶病?
與Epoch
有關会通。
輕量級鎖
輕量級鎖是為了在線程交替執(zhí)行同步代碼塊時提高性能,偏向鎖是在只有一個線程執(zhí)行同步塊時提高性能娄周。
輕量級鎖與偏向鎖的不同
:輕量級鎖每次退出同步代碼塊都要釋放鎖
涕侈,偏向鎖只有在競爭發(fā)生時才會釋放鎖。
加鎖過程
:
a.線程進入同步代碼塊時確定對象鎖狀態(tài):即對象鎖狀態(tài)是無鎖狀態(tài)
:對象頭中Mark Word中的鎖標志位為:“01”狀態(tài)煤辨,偏向鎖標志位為:“0”驾凶。
b.確定對象鎖的對象頭為無鎖狀態(tài)后,在當前線程的棧幀中創(chuàng)建鎖記錄(Lock Record)空間掷酗,將鎖對象對象頭中的Mark Word復制到鎖記錄中
。
c.Mark Word保存到鎖記錄成功后窟哺,使用CAS操作嘗試將對象的Mark Word更新為指向Lock Record的指針并將Lock Record鎖記錄里的owner指向對象的Mark Word
泻轰。
CAS操作成功了
:線程就擁有了對象的鎖可以去執(zhí)行同步代碼塊,并會將對象的鎖標志設置為:“00”且轨,即為輕量級鎖浮声。
CAS操作失敗了
:先檢查對象頭Mark Word的指針有沒有指向當前線程的鎖記錄Lock Record虚婿,指向了說明當前線程已經獲得了鎖,本次加鎖是重入鎖泳挥,當前線程可以直接進入同步塊繼續(xù)執(zhí)行然痊;否則線程會進入自旋狀態(tài),不斷嘗試去獲取鎖屉符。
- 輕量級鎖每次進入與退出同步塊時都要通過CAS操作更新對象頭剧浸。
鎖釋放過程
:
d.線程退出同步代碼塊并釋放鎖是通過CAS操作來完成的,會用CAS操作將對象頭中的Mark Word中記錄的鎖對象的指針替換為鎖記錄中復制的Mark Word
矗钟。
CAS操作成功
:同步操作完成唆香,對象頭設置為無鎖狀態(tài)(偏向鎖為:“0”,鎖標志位為:“01”)吨艇。
CAS操作失敗
:此時持有輕量級鎖的線程會將鎖釋放躬它,喚醒阻塞的線程。
CAS操作失敗的原因:是因為沒有得到鎖的線程在超過了自旋次數(shù)還沒有獲得鎖东涡,進入阻塞狀態(tài)冯吓,并將對象頭的鎖標志更改為:“10”(重量級鎖),Mark Word中存儲的是指向指向重量級鎖的指針(互斥量)
疮跑。
獲得和釋放輕量級鎖的過程圖如下:
問題1
:為什么在升級為輕量級鎖時要把對象頭里的Mark Word復制到線程棧的鎖記錄中组贺?
在獲得鎖時:會將Mark Word值作為CAS比較的條件(CAS操作的三個值中的當前值)。只有鎖記錄復制的Mark Word值與對象鎖的對象頭中的Mark Word相等時祸挪,才會將對象頭中的Mark Word中的內容替換為指向鎖記錄的指針锣披,才可以獲得鎖。
在釋放鎖時:通過該Mark Word可以判定是否在持有鎖的過程中該對象鎖被其他線程申請過贿条,如果被申請過雹仿,則在釋放鎖的過程中喚醒被掛起的線程。
問題2
:為什么會嘗試CAS不成功以及什么情況下會不成功整以?
重量級鎖
如果在存在同一時間訪問同一鎖胧辽,就會導致輕量級鎖膨脹為重量級鎖。
對象鎖升級為重量級鎖時公黑,鎖標志的狀態(tài)值變?yōu)?10"邑商,Mark Word中存儲的是指向重量級鎖的指針(指針指向的是Monitor對象的起始地址
),此時等待鎖的線程都會進入阻塞狀態(tài)凡蚜。
重量級鎖通過對象內部的monitor監(jiān)視器鎖來實現(xiàn)人断。監(jiān)視器鎖是依賴于底層操作系統(tǒng)的Mutex Lock(互斥鎖)來實現(xiàn)。
總結:偏向鎖是通過對比Mark Word
解決加鎖問題朝蜘,避免多次執(zhí)行CAS操作恶迈;輕量級鎖通過CAS操作和自旋
解決加鎖問題,避免線程阻塞和喚醒而影響性能谱醇;重量級鎖是將除了擁有鎖的線程以外的線程都阻塞暇仲。
- 對synchronized實現(xiàn)機制進行的其他調整
鎖消除
在進行同步控制時步做,JVM檢測到不可能存在共享數(shù)據(jù)競爭,JVM就會對同步鎖進行鎖清除
奈附。
鎖清除的依據(jù)是:逃逸分析的數(shù)據(jù)支持全度。
鎖粗化
將多個連續(xù)的加鎖、解鎖操作連接在一起斥滤,擴展成一個范圍更大的鎖将鸵。