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