一、什么是管程(Monitor)
管程:指管理共享變量以及對共享變量的操作過程,讓它們支持并發。
信號量:操作系統提供的一種協調共享資源的訪問方法,地位高于進程。
管程和信號量是等價的,即管程能夠實現信號量,信號量也能夠實現管程。java 采用的是管程技術,synchronized 關鍵字及wait()、notify()、notifyAll() 三個方法都是管程的組成部分。
管程模型包括:MESA 模型、Hasen 模型、Hoare 模型。MESA 模型才用更加廣泛。
并發編程的兩大核心問題:互斥與同步,互斥即同一時刻只允許有一個線程能夠訪問共享資源;異步即兩個線程之間如何通信、協作。兩個問題管程都能解決。
二、管程模型
管程解決互斥:將共享變量及對共享變量的操作都統一封裝起來。如果想要訪問共享變量則只能通過管程封裝的對共享變量的操作方法執行。
管程解決同步:
管程中引入了條件變量的概念:每個條件變量都對應有一個等待隊列。條件變量和等待隊列就是解決線程同步的問題。
假設有個線程 T1 執行出隊(jdk 里面的阻塞隊列,里面存的是共享數據)操作,不過需要注意的是執行出隊操作,有個前提條件,就是隊列不能是空的,而隊列不空這個前提條件就是管程里的條件變量。
如果線程 T1 進入管程后恰好發現隊列(沒有共享數據可操作)是空的,那怎么辦呢?等待啊,去哪里等呢?就去條件變量對應的等待隊列里面等(等待阻塞隊列里面有共享數據)。
此時線程 T1 就去“隊列不空”這個條件變量的等待隊列中等待。這個過程類似于大夫發現你要去驗個血,于是給你開了個驗血的單子,你呢就去驗血的隊伍里排隊(阻塞隊列里面等待,等待時間操作共享變量)。
線程 T1 進入條件變量的等待隊列后,是允許其他線程進入管程的。這和你去驗血的時候,醫生可以給其他患者診治,道理都是一樣的。
解釋:剛開始一直在想線程T1執行出隊是什么意思?到底是哪個隊列,是入口等待隊列,還是條件等待隊列,后來理解了都不是。這個隊列應該理解為JDK里面的阻塞隊列,里面存在的是共享數據,線程T1,T2分別去操作里面的共享數據,執行數據的入隊,出隊操作,當然這些操作是阻塞操作。當線程T1對阻塞隊列執行數據出隊操作時,進入管程,發現阻塞隊列為空,此時線程T1進入阻塞隊列不為空這個條件的條件等待隊列,此時,其他線程還是可以進入管程的,比如T2進來了,對阻塞隊列執行數據插入操作,這時就會致使線程T1從條件等待隊列出來,進入入口等待隊列,準備再一次進入管程
public class BlockedQueue<T>{
? final Lock lock =? new ReentrantLock();
? // 條件變量:隊列不滿?
? final Condition notFull =? lock.newCondition();
? // 條件變量:隊列不空?
? final Condition notEmpty = lock.newCondition();
? // 入隊
? void enq(T x) {
? ? lock.lock();
? ? try {
? ? ? while (隊列已滿){
? ? ? ? // 等待隊列不滿
? ? ? ? notFull.await();
? ? ? }?
? ? ? // 省略入隊操作...
? ? ? //入隊后,通知可出隊
? ? ? notEmpty.signal();
? ? }finally {
? ? ? lock.unlock();
? ? }
? }
? // 出隊
? void deq(){
? ? lock.lock();
? ? try {
? ? ? while (隊列已空){
? ? ? ? // 等待隊列不空
? ? ? ? notEmpty.await();
? ? ? }
? ? ? // 省略出隊操作...
? ? ? //出隊后,通知可入隊
? ? ? notFull.signal();
? ? }finally {
? ? ? lock.unlock();
? ? }?
? }
}
await() 和前面我們提到的 wait() 語義是一樣的;signal() 和前面我們提到的 notify() 語義是一樣的。
三、notify ()的正確使用
盡量使用notify(),除非經過深思熟慮。
滿足以下三個條件可以使用notify():
1. 所有等待線程擁有相同的等待條件;
2. 所有等待線程被喚醒后,執行相同的操作;
3. 只需喚醒一個線程。
四、總結
java 參考了MESA 模型,但java 內置的管程變量只有一個條件變量。
java 內置的管程方案(synchronized)在編譯的時候自動生成相關的加鎖和解鎖代碼,但僅支持一個條件變量。
java sdk 并發包工具的管程支持多個條件變量,不過并發包里的鎖需要開發人員自己進行加鎖和解鎖的操作。
并發編程的兩大核心 —— 互斥和同步,都可以由管程來幫忙解決。