Monitor对象是什么?
synchronized保證線程同步的作用相信大家都已經(jīng)非常熟悉了,可以把任意一個(gè)對(duì)象當(dāng)作鎖。synchronized 關(guān)鍵字無(wú)論是修飾代碼塊,還是修飾實(shí)例方法和靜態(tài)方法,本質(zhì)上都是作用于對(duì)象上。
多個(gè)線程要競(jìng)爭(zhēng)共享資源,而操作共享資源資源的代碼就在臨界區(qū)內(nèi),想要進(jìn)入到這個(gè)臨界區(qū)就必須持有鎖。
當(dāng)用 synchronized 修飾代碼塊時(shí),編譯后的字節(jié)碼會(huì)有 monitorenter 和 monitorexit 指令,分別對(duì)應(yīng)的是獲得鎖和解鎖。
當(dāng)用 synchronized 修飾方法時(shí),會(huì)給方法加上標(biāo)記 ACC_SYNCHRONIZED,這樣 JVM 就知道這個(gè)方法是一個(gè)同步方法,于是在進(jìn)入同步方法的時(shí)候就會(huì)進(jìn)行執(zhí)行競(jìng)爭(zhēng)鎖的操作,只有拿到鎖才能繼續(xù)執(zhí)行。
對(duì)象鎖長(zhǎng)啥樣?
那么對(duì)象鎖在內(nèi)存中是怎樣的呢?接下來(lái)就來(lái)看一下對(duì)象鎖的實(shí)現(xiàn)細(xì)節(jié)。對(duì)象鎖的狀態(tài)是記錄在對(duì)象頭中的Mark word區(qū)域中。關(guān)于對(duì)象的內(nèi)存區(qū)域的細(xì)節(jié),大家可以參考前文《Java面試必考問(wèn)題:對(duì)象在內(nèi)存中是如何布局的? 》。
在不同的鎖狀態(tài)下,Mark word會(huì)存儲(chǔ)不同的信息,這也是為了節(jié)約內(nèi)存常用的設(shè)計(jì)。當(dāng)鎖狀態(tài)為重量級(jí)鎖(鎖標(biāo)識(shí)位=10)時(shí),Mark word中會(huì)記錄指向Monitor對(duì)象的指針,這個(gè)Monitor對(duì)象也稱為管程或監(jiān)視器鎖
每個(gè)對(duì)象都存在著一個(gè) Monitor對(duì)象與之關(guān)聯(lián)。執(zhí)行 monitorenter 指令就是線程試圖去獲取 Monitor 的所有權(quán),搶到了就是成功獲取鎖了;執(zhí)行 monitorexit 指令則是釋放了Monitor的所有權(quán)
ObjectMonitor類
在HotSpot虛擬機(jī)中,Monitor是基于C++的ObjectMonitor類實(shí)現(xiàn)的,其主要成員包括:
- _owner:指向持有ObjectMonitor對(duì)象的線程
- _WaitSet:存放處于wait狀態(tài)的線程隊(duì)列,即調(diào)用wait()方法的線程
- _EntryList:存放處于等待鎖block狀態(tài)的線程隊(duì)列
- _count:約為_(kāi)WaitSet 和 _EntryList 的節(jié)點(diǎn)數(shù)之和
- _cxq: 多個(gè)線程爭(zhēng)搶鎖,會(huì)先存入這個(gè)單向鏈表
- _recursions: 記錄重入次數(shù)
上圖簡(jiǎn)略展示了ObjectMonitor的基本工作機(jī)制:
(1)當(dāng)多個(gè)線程同時(shí)訪問(wèn)一段同步代碼時(shí),首先會(huì)進(jìn)入 _EntryList 隊(duì)列中。
(2)當(dāng)某個(gè)線程獲取到對(duì)象的Monitor后進(jìn)入臨界區(qū)域,并把Monitor中的 _owner 變量設(shè)置為當(dāng)前線程,同時(shí)Monitor中的計(jì)數(shù)器 _count 加1。即獲得對(duì)象鎖。
(3)若持有Monitor的線程調(diào)用 wait() 方法,將釋放當(dāng)前持有的Monitor,_owner變量恢復(fù)為null,_count自減1,同時(shí)該線程進(jìn)入 _WaitSet 集合中等待被喚醒。
(4)在_WaitSet 集合中的線程會(huì)被再次放到_EntryList 隊(duì)列中,重新競(jìng)爭(zhēng)獲取鎖。
(5)若當(dāng)前線程執(zhí)行完畢也將釋放Monitor并復(fù)位變量的值,以便其他線程進(jìn)入獲取鎖。
線程爭(zhēng)搶鎖的過(guò)程要比上面展示得更加復(fù)雜。除了_EntryList 這個(gè)雙向鏈表用來(lái)保存競(jìng)爭(zhēng)的線程,ObjectMonitor中還有另外一個(gè)單向鏈表 _cxq,由兩個(gè)隊(duì)列來(lái)共同管理并發(fā)的線程。
ObjectMonitor::enter() 和 ObjectMonitor::exit() 分別是ObjectMonitor獲取鎖和釋放鎖的方法。線程解鎖后還會(huì)喚醒之前等待的線程,根據(jù)策略選擇直接喚醒_cxq隊(duì)列中的頭部線程去競(jìng)爭(zhēng),或者將_cxq隊(duì)列中的線程加入_EntryList,然后再喚醒_EntryList隊(duì)列中的線程去競(jìng)爭(zhēng)。
ObjectMonitor::enter()
下面我們看一下ObjectMonitor::enter()方法競(jìng)爭(zhēng)鎖的流程:
首先嘗試通過(guò) CAS 把 ObjectMonitor 中的 _owner 設(shè)置為當(dāng)前線程,設(shè)置成功就表示獲取鎖成功。通過(guò) _recursions 的自增來(lái)表示重入。
如果沒(méi)有CAS成功,那么就開(kāi)始啟動(dòng)自適應(yīng)自旋,自旋還不行的話,就包裝成 ObjectWaiter 對(duì)象加入到 _cxq 單向鏈表之中。關(guān)于自旋鎖和自適應(yīng)自旋,可以參考前文《Java面試必考問(wèn)題:什么是自旋鎖 》](https://www.toutiao.com/i6934327407897854475/?group_id=6934327407897854475)。
加入_cxq鏈表后,再次嘗試是否可以CAS拿到鎖,再次失敗就要阻塞(block),底層調(diào)用了pthread_mutex_lock。
ObjectMonitor::exit()方法
線程執(zhí)行 Object.wait()方法時(shí),會(huì)將當(dāng)前線程加入到 _waitSet 這個(gè)雙向鏈表中,然后再運(yùn)行ObjectMonitor::exit() 方法來(lái)釋放鎖。
可重入鎖就是根據(jù) _recursions 來(lái)判斷的,重入一次就執(zhí)行 _recursions++,解鎖一次就執(zhí)行 _recursions–,如果 _recursions 減到 0 ,就說(shuō)明需要釋放鎖了。
線程解鎖后還會(huì)喚醒之前等待的線程。當(dāng)線程執(zhí)行 Object.notify()方法時(shí),從 _waitSet 頭部拿線程節(jié)點(diǎn),然后根據(jù)策略(QMode指定)決定將線程節(jié)點(diǎn)放在哪里,包括_cxq 或 _EntryList 的頭部或者尾部,然后喚醒隊(duì)列中的線程。
總結(jié)
以上是生活随笔為你收集整理的Monitor对象是什么?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: pug安装与使用教程
- 下一篇: 并行计算,网格计算与分布式计算的…