synchronized 是 Java 的關(guān)鍵字之一鸟辅,提供一種原子性的內(nèi)部鎖呕臂,Java 中的每個(gè)對象都可以把它當(dāng)作一個(gè)同步鎖使用师郑,這種 Java 內(nèi)置的使用者看不到的鎖為內(nèi)部所添忘,或叫監(jiān)視器鎖察皇。
[TOC]
synchronized 內(nèi)存語義
進(jìn)入 synchronized
塊不傅,即是當(dāng)前線程會(huì)從自己的工作內(nèi)存中清除 synchronized
塊中使用到的變量旅掂,并從主內(nèi)存中加載。
退出 synchronized
塊访娶,即是當(dāng)前線程將對 synchronized
塊中使用到的變量作出的修改刷新到主存上商虐。
Monitor
簡介
即上述所提到的監(jiān)視器鎖中的監(jiān)視器(monitor),也會(huì)被翻譯為管程震肮。在操作系統(tǒng)中称龙,面對多線程操作時(shí),通常會(huì)用到 semaphore
(信號(hào)量)和mutex
(互斥)這兩個(gè)重要的同步原語戳晌,但直接操作這兩者會(huì)非常復(fù)雜鲫尊。為了能夠更容易編寫正確程序,在二者的基礎(chǔ)上沦偎,提出了monitor
疫向。
需要注意咳蔚,操作系統(tǒng)本身不支持monitor
,monitor
的支持屬于編程語言范圍搔驼,例如 C 語言不支持monitor
谈火,而 Java 是支持的。對于不同語言舌涨,monitor
的實(shí)現(xiàn)方式也不一樣糯耍。
monitor
的發(fā)展史上,有三種不同的模型:
- Hasen模型
- Hoare模型
- MESA模型
后面提到的 monitor
均指 Java 中實(shí)現(xiàn)的 monitor
Java 則參考了 MESA 模型進(jìn)行實(shí)現(xiàn)囊嘉,在 JVM 使用的 HostSpot 模擬機(jī)中温技,monitor
是由 C++ 來實(shí)現(xiàn)的,主要數(shù)據(jù)結(jié)構(gòu)如下:
ObjectMonitor() {
_header = NULL;
_count = 0; //monitor進(jìn)入數(shù)
_waiters = 0,
_recursions = 0; //線程的重入次數(shù)
_object = NULL;
_owner = NULL; //標(biāo)識(shí)擁有該monitor的線程
_WaitSet = NULL; //等待線程組成的雙向循環(huán)鏈表扭粱,_WaitSet是第一個(gè)節(jié)點(diǎn)
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; //多線程競爭鎖進(jìn)入時(shí)的單項(xiàng)鏈表
FreeNext = NULL ;
_EntryList = NULL ; //處于等待鎖block狀態(tài)的線程舵鳞,會(huì)被加入到該列表
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
使用 synchronized
關(guān)鍵字,通常需要指定一個(gè)對象琢蛤,稱作monitor object
蜓堕。HotSpot 會(huì)自動(dòng)創(chuàng)建與該對象關(guān)聯(lián)的monitor
對象。
特點(diǎn)
同一時(shí)刻博其,monitor
僅支持一個(gè) 進(jìn)程 / 線程進(jìn)入monitor
定義的臨界區(qū)(達(dá)成互斥)套才。同時(shí),還需要對未能進(jìn)入臨界區(qū)的 進(jìn)程 / 線程 進(jìn)行管理贺奠,例如 阻塞 和 喚醒霜旧。monitor
作為一個(gè)同步工具,能夠提供管理 進(jìn)程 / 線程狀態(tài)的機(jī)制儡率。
如若
semaphore
(信號(hào)量)和mutex
(互斥)挂据,還需要使用者對上述機(jī)制進(jìn)行實(shí)現(xiàn),而monitor
在內(nèi)部進(jìn)行了實(shí)現(xiàn)儿普,對外提供了更簡潔易用的接口崎逃。
當(dāng)線程需要獲取鎖時(shí),會(huì)放入 Entry Set
進(jìn)行等待眉孩。
如果線程得到鎖个绍,則稱為當(dāng)前鎖的Owner
。
若獲得鎖的線程運(yùn)行時(shí)發(fā)現(xiàn)需要等待外部條件浪汪,則可調(diào)用wait
方法巴柿,進(jìn)入Wait Set
等待。
進(jìn)入Wait Set
的線程會(huì)被notify
的調(diào)用喚醒死遭,重新嘗試獲取鎖變?yōu)?code>Owner广恢。
還需要留意一點(diǎn)時(shí),線程出入臨界區(qū)時(shí)呀潭,會(huì)分別執(zhí)行moniterenter
/moniterexist
系統(tǒng)調(diào)用钉迷,同時(shí) Java 線程實(shí)際上是對操作系統(tǒng)線程的映射至非。因此在 Java SE1.6 前,使用synchronized
會(huì)造成線程狀態(tài)切換(用戶態(tài)和內(nèi)核態(tài))糠聪,增大運(yùn)行開銷荒椭。在 Java SE1.6 后,synchronized
得到優(yōu)化舰蟆,引入了 偏向鎖趣惠、輕量級(jí)鎖。
對象內(nèi)存模型中的鎖標(biāo)記
在此夭苗,需要了解一下 Java 對象的內(nèi)存模型信卡,Java 的對象構(gòu)成有三部分:
- 對象頭:包含 Mark Word(標(biāo)記)隔缀、Class Pointer (類信息)
- 實(shí)例數(shù)據(jù)
- 對齊填充
其中對象頭的 MarkWord 共 32 bit 题造,包含了鎖的狀態(tài)信息,可見 Mark Word 中包含了四種鎖狀態(tài):
- 無鎖
- 偏向鎖
- 輕量級(jí)鎖
- 重量級(jí)鎖
鎖會(huì)根據(jù)競爭條件的不同進(jìn)行對應(yīng)上面四種狀態(tài)猾瘸,由上至下進(jìn)行升級(jí)界赔,且不能降級(jí)。
鎖升級(jí)過程可以參考:https://www.bilibili.com/video/BV1xT4y1A7kA?spm_id_from=333.999.0.0
參考鏈接
https://blog.csdn.net/TZ845195485/article/details/108099961