【学习笔记】monitor
monitor的概念
管程,英文是 Monitor,也常被翻譯為“監視器”,monitor 不管是翻譯為“管程”還是“監視器”,都是比較晦澀的,通過翻譯后的中文,并無法對 monitor 達到一個直觀的描述
在《操作系統同步原語》 這篇文章中,介紹了操作系統在面對進程/線程間同步的時候,所支持的一些同步原語,其中 semaphore 信號量 和 mutex 互斥量是最重要的同步原語
在使用基本的 mutex 進行并發控制時,需要程序員非常小心地控制 mutex 的 down 和 up 操作,否則很容易引起死鎖等問題。為了更容易地編寫出正確的并發程序,所以在 mutex 和 semaphore 的基礎上,提出了更高層次的同步原語 monitor,不過需要注意的是,操作系統本身并不支持 monitor 機制,實際上,monitor 是屬于編程語言的范疇,當你想要使用 monitor 時,先了解一下語言本身是否支持 monitor 原語,例如 C 語言它就不支持 monitor,Java 語言支持 monitor
一般的 monitor 實現模式是編程語言在語法上提供語法糖,而如何實現 monitor 機制,則屬于編譯器的工作,Java 就是這么干的
monitor 的重要特點是,同一個時刻,只有一個進程/線程能進入 monitor 中定義的臨界區,這使得 monitor 能夠達到互斥的效果。但僅僅有互斥的作用是不夠的,無法進入 monitor 臨界區的進程/線程,它們應該被阻塞,并且在必要的時候會被喚醒。顯然,monitor 作為一個同步工具,也應該提供這樣的管理進程/線程狀態的機制。想想我們為什么覺得 semaphore 和 mutex 在編程上容易出錯,因為我們需要去親自操作變量以及對進程/線程進行阻塞和喚醒。monitor 這個機制之所以被稱為“更高級的原語”,那么它就不可避免地需要對外屏蔽掉這些機制,并且在內部實現這些機制,使得使用 monitor 的人看到的是一個簡潔易用的接口
monitor元素
monitor 是線程私有的數據結構,每一個線程都有一個可用 monitor record 列表,同時還有一個全局的可用列表。每一個被鎖住的對象都會和一個 monitor 關聯(對象頭的MarkWord中的LockWord指向monitor的起始地址),同時 monitor 中有一個 Owner 字段存放擁有該鎖的線程的唯一標識,表示該鎖被這個線程占用,先來看 monitor 的內部:
- Owner:初始時為 NULL 表示當前沒有任何線程擁有該 monitor,當線程成功擁有該鎖后保存線程唯一標識,當鎖被釋放時又設置為 NULL
- EntryQ:關聯一個系統互斥鎖(semaphore),阻塞所有試圖鎖住 monitor 失敗的線程
- RcThis:表示 blocked 或 waiting 在該 monitor 上的所有線程的個數
- Nest:用來實現重入鎖的計數
- HashCode:保存從對象頭拷貝過來的 HashCode 值(可能還包含GC age)
- Candidate:用來避免不必要的阻塞或等待線程喚醒,因為每一次只有一個線程能夠成功擁有鎖,如果每次前一個釋放鎖的線程喚醒所有正在阻塞或等待的線程,會引起不必要的上下文切換(從阻塞到就緒然后因為競爭鎖失敗又被阻塞)從而導致性能嚴重下降。Candidate 只有兩種可能的值:0表示沒有需要喚醒的線程,1表示要喚醒一個繼任線程來競爭鎖
monitor機制
monitor 機制需要幾個元素來配合,分別是:
使用 monitor 機制的目的主要是為了互斥進入臨界區,為了做到能夠阻塞無法進入臨界區的進程/線程,還需要一個 monitor object 來協助,這個 monitor object 內部會有相應的數據結構,例如列表,來保存被阻塞的線程;同時由于 monitor 機制本質上是基于 mutex 這種基本原語的,所以 monitor object 還必須維護一個基于 mutex 的鎖
此外,為了在適當的時候能夠阻塞和喚醒進程/線程,還需要引入一個條件變量,這個條件變量用來決定什么時候是“適當的時候”,這個條件可以來自程序代碼的邏輯,也可以是在 monitor object 的內部,總而言之,程序員對條件變量的定義有很大的自主性。不過,由于 monitor object 內部采用了數據結構來保存被阻塞的隊列,因此它也必須對外提供兩個 API 來讓線程進入阻塞狀態以及之后被喚醒,分別是 wait 和 notify
Java 語言對 monitor 的支持
monitor 是操作系統提出來的一種高級原語,但其具體的實現模式,不同的編程語言都有可能不一樣。以下以 Java 的 monitor 為例子,來講解 monitor 在 Java 中的實現方式
臨界區的圈定
在 Java 中,可以采用 synchronized 關鍵字來修飾實例方法、類方法以及代碼塊,如下所示:
/*** @author beanlam* @version 1.0* @date 2018/9/12*/ public class Monitor {private Object ANOTHER_LOCK = new Object();private synchronized void fun1() {}public static synchronized void fun2() {}public void fun3() {synchronized (this) {}}public void fun4() {synchronized (ANOTHER_LOCK) {}}}被 synchronized 關鍵字修飾的方法、代碼塊,就是 monitor 機制的臨界區
monitor object
可以發現,上述的 synchronized 關鍵字在使用的時候,往往需要指定一個對象與之關聯,例如 synchronized(this),或者 synchronized(ANOTHER_LOCK),synchronized 如果修飾的是實例方法,那么其關聯的對象實際上是 this,如果修飾的是類方法,那么其關聯的對象是 this.class。總之,synchronzied 需要關聯一個對象,而這個對象就是 monitor object
monitor 的機制中,monitor object 充當著維護 mutex以及定義 wait/signal API 來管理線程的阻塞和喚醒的角色
Java 語言中的 java.lang.Object 類,便是滿足這個要求的對象,任何一個 Java 對象都可以作為 monitor 機制的 monitor object
Java 對象存儲在內存中,分別分為三個部分,即對象頭、實例數據和對齊填充,而在其對象頭中,保存了鎖標識;同時,java.lang.Object 類定義了 wait(),notify(),notifyAll() 方法,這些方法的具體實現,依賴于一個叫 ObjectMonitor 模式的實現,這是 JVM 內部基于 C++ 實現的一套機制,基本原理如下所示:
當一個線程需要獲取 Object 的鎖時,會被放入 EntrySet 中進行等待,如果該線程獲取到了鎖,成為當前鎖的 owner。如果根據程序邏輯,一個已經獲得了鎖的線程缺少某些外部條件,而無法繼續進行下去(例如生產者發現隊列已滿或者消費者發現隊列為空),那么該線程可以通過調用 wait 方法將鎖釋放,進入 wait set 中阻塞進行等待,其它線程在這個時候有機會獲得鎖,去干其它的事情,從而使得之前不成立的外部條件成立,這樣先前被阻塞的線程就可以重新進入 EntrySet 去競爭鎖。這個外部條件在 monitor 機制中稱為條件變量
synchronized 關鍵字
synchronized 關鍵字是 Java 在語法層面上,用來讓開發者方便地進行多線程同步的重要工具。要進入一個 synchronized 方法修飾的方法或者代碼塊,會先獲取與 synchronized 關鍵字綁定在一起的 Object 的對象鎖,這個鎖也限定了其它線程無法進入與這個鎖相關的其它 synchronized 代碼區域
Java 提供的 monitor 機制,其實是 Object,synchronized 等元素合作形成的,甚至說外部的條件變量也是個組成部分。JVM 底層的 ObjectMonitor 只是用來輔助實現 monitor 機制的一種常用模式,但大多數文章把 ObjectMonitor 直接當成了 monitor 機制
我覺得應該這么理解:Java 對 monitor 的支持,是以機制的粒度提供給開發者使用的,也就是說,開發者要結合使用 synchronized 關鍵字,以及 Object 的 wait / notify 等元素,才能說自己利用 monitor 的機制去解決了一個生產者消費者的問題
運行狀態—wait—>等待阻塞—notify—>同步阻塞
參考
- Monitor(管程)是什么意思?Java中Monitor(管程)的介紹
- JVM內部細節之一:synchronized關鍵字及實現細節(輕量級鎖Lightweight Locking)
總結
以上是生活随笔為你收集整理的【学习笔记】monitor的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 王之泰201771010131《面向对象
- 下一篇: 安装JDK1.8报错 “当前页面的脚本发