管程,英文是 Monitor,也常被翻譯爲「監視器」,monitor 不論是翻譯爲「管程」仍是「監視器」,都是比較晦澀的,經過翻譯後的中文,並沒有法對 monitor 達到一個直觀的描述。
在《操做系統同步原語》 這篇文章中,介紹了操做系統在面對 進程/線程 間同步的時候,所支持的一些同步原語,其中 semaphore 信號量 和 mutex 互斥量是最重要的同步原語。
在使用基本的 mutex 進行併發控制時,須要程序員很是當心地控制 mutex 的 down 和 up 操做,不然很容易引發死鎖等問題。爲了更容易地編寫出正確的併發程序,因此在 mutex 和 semaphore 的基礎上,提出了更高層次的同步原語 monitor,不過須要注意的是,操做系統自己並不支持 monitor 機制,實際上,monitor 是屬於編程語言的範疇,當你想要使用 monitor 時,先了解一下語言自己是否支持 monitor 原語,例如 C 語言它就不支持 monitor,Java 語言支持 monitor。
通常的 monitor 實現模式是編程語言在語法上提供語法糖,而如何實現 monitor 機制,則屬於編譯器的工做,Java 就是這麼幹的。java
monitor 的重要特色是,同一個時刻,只有一個 進程/線程 能進入 monitor 中定義的臨界區,這使得 monitor 可以達到互斥的效果。但僅僅有互斥的做用是不夠的,沒法進入 monitor 臨界區的 進程/線程,它們應該被阻塞,而且在必要的時候會被喚醒。顯然,monitor 做爲一個同步工具,也應該提供這樣的管理 進程/線程 狀態的機制。想一想咱們爲何以爲 semaphore 和 mutex 在編程上容易出錯,由於咱們須要去親自操做變量以及對 進程/線程 進行阻塞和喚醒。monitor 這個機制之因此被稱爲「更高級的原語」,那麼它就不可避免地須要對外屏蔽掉這些機制,而且在內部實現這些機制,使得使用 monitor 的人看到的是一個簡潔易用的接口。程序員
monitor 機制須要幾個元素來配合,分別是:編程
使用 monitor 機制的目的主要是爲了互斥進入臨界區,爲了作到可以阻塞沒法進入臨界區的 進程/線程,還須要一個 monitor object 來協助,這個 monitor object 內部會有相應的數據結構,例如列表,來保存被阻塞的線程;同時因爲 monitor 機制本質上是基於 mutex 這種基本原語的,因此 monitor object 還必須維護一個基於 mutex 的鎖。
此外,爲了在適當的時候可以阻塞和喚醒 進程/線程,還須要引入一個條件變量,這個條件變量用來決定何時是「適當的時候」,這個條件能夠來自程序代碼的邏輯,也能夠是在 monitor object 的內部,總而言之,程序員對條件變量的定義有很大的自主性。不過,因爲 monitor object 內部採用了數據結構來保存被阻塞的隊列,所以它也必須對外提供兩個 API 來讓線程進入阻塞狀態以及以後被喚醒,分別是 wait 和 notify。微信
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 機制的臨界區。併發
能夠發現,上述的 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 關鍵字是 Java 在語法層面上,用來讓開發者方便地進行多線程同步的重要工具。要進入一個 synchronized 方法修飾的方法或者代碼塊,會先獲取與 synchronized 關鍵字綁定在一塊兒的 Object 的對象鎖,這個鎖也限定了其它線程沒法進入與這個鎖相關的其它 synchronized 代碼區域。this
網上不少文章以及資料,在分析 synchronized 的原理時,基本上都會說 synchronized 是基於 monitor 機制實現的,但不多有文章說清楚,都是模糊帶過。
參照前面提到的 Monitor 的幾個基本元素,若是 synchronized 是基於 monitor 機制實現的,那麼對應的元素分別是什麼?
它必需要有臨界區,這裏的臨界區咱們能夠認爲是對對象頭 mutex 的 P 或者 V 操做,這是個臨界區
那 monitor object 對應哪一個呢?mutex?總之沒法找到真正的 monitor object。
因此我認爲「synchronized 是基於 monitor 機制實現的」這樣的說法是不正確的,是模棱兩可的。
Java 提供的 monitor 機制,實際上是 Object,synchronized 等元素合做造成的,甚至說外部的條件變量也是個組成部分。JVM 底層的 ObjectMonitor 只是用來輔助實現 monitor 機制的一種經常使用模式,但大多數文章把 ObjectMonitor 直接當成了 monitor 機制。
我以爲應該這麼理解:Java 對 monitor 的支持,是以機制的粒度提供給開發者使用的,也就是說,開發者要結合使用 synchronized 關鍵字,以及 Object 的 wait / notify 等元素,才能說本身利用 monitor 的機制去解決了一個生產者消費者的問題。