說明
Monitor,直譯為“監視器”,而作業系統領域一般翻譯為“管程”,管程是指管理共享變數以及對共享變數操作的程序,讓它們支持并發,在Java 1.5之前,Java語言提供的唯一并發語言就是管程,Java 1.5之后提供的SDK并發包也是以管程為基礎的,除了Java之外,C/C++、C#等高級語言也都是支持管程的,synchronized關鍵字和wait()、notify()、notifyAll()這三個方法是Java中實作管程技術的組成部分,
MESA模型分析
在管程的發展史上,先后出現過三種不同的管程模型,分別是Hasen模型、Hoare模型和 MESA模型,現在正在廣泛使用的是MESA模型,下面我們便介紹MESA模型:
管程中引入了條件變數的概念,而且每個條件變數都對應有一個等待佇列,條件變數和等待 佇列的作用是解決執行緒之間的同步問題,
分析作用:
入口等待佇列:試圖要獲取鎖的執行緒都必須要進入到這個佇列,只有在這個佇列里面才能獲取鎖,而且每次都是獲取到鎖才會出隊,
條件變數等待佇列:這個是為已獲得鎖的成員進入等待阻塞準備的,當需要等待條件滿足時,為了更好的利用CPU,讓執行緒進入等待阻塞,而什么時候再次獲得鎖,也就是當等待的條件滿足了,就會從這個佇列中出去,進入到入口等待佇列中,再次獲取鎖,
wait()的正確使用姿勢
對于MESA管程來說,有一個編程范式:
while(條件不滿足) { wait(); }
喚醒的時間和獲取到鎖繼續執行的時間是不一致的,被喚醒的執行緒再次執行時可能條件又不滿足了,所以回圈檢驗條件,MESA模型的wait()方法還有一個超時引數,為了避免執行緒進入等待 佇列永久阻塞,
notify()和notifyAll()分別何時使用
滿足以下三個條件時,可以使用notify(),其余情況盡量使用notifyAll():
- 所有等待執行緒擁有相同的等待條件;
- 所有等待執行緒被喚醒后,執行相同的操作;
- 只需要喚醒一個執行緒,
要知道notify()是隨機喚醒一個,而notifyAll()則是喚醒全部,如果是要喚醒特定的執行緒,最好用notifyAll() + while(條件不滿足)來保證指定執行緒會被喚醒,
實際案例:Java語言的內置管程synchronized
Java 參考了 MESA 模型,語言內置的管程(synchronized)對 MESA 模型進行了精簡,MESA模型中,條件變數可以有多個,Java 語言內置的管程里只有一個條件變數,模型如下圖所示:

Monitor機制在Java中的實作
說明
java.lang.Object 類定義了 wait(),notify(),notifyAll() 方法,這些方法的具體實作,依賴于 ObjectMonitor 實作,這是 JVM 內部基于 C++ 實作的一套機制,
所謂ObjectMonitor ,是獨立的物件監視器,其中的_object便是用于存盤synchronized (lock)中的lock,
ObjectMonitor其主要資料結構如下(hotspot原始碼ObjectMonitor.hpp):
ObjectMonitor() { _header = NULL; //物件頭 markOop _count = 0; _waiters = 0, _recursions = 0; // 鎖的重入次數 _object = NULL; //存盤鎖物件 _owner = NULL; // 標識擁有該monitor的執行緒(當前獲取鎖的執行緒) _WaitSet = NULL; // 等待執行緒(呼叫wait)組成的雙向回圈鏈表,_WaitSet是第一個節點 _WaitSetLock = 0; _Responsible = NULL ; _succ = NULL ; _cxq = NULL ; //多執行緒競爭鎖會先存到這個單向鏈表中 (FILO堆疊結構) FreeNext = NULL ; _EntryList = NULL ; //存放在進入或重新進入時被阻塞(blocked)的執行緒 (也是存競爭鎖失敗的執行緒) _SpinFreq = 0; _SpinClock = 0; OwnerIsThread = 0; _previous_owner_tid = 0; }
圖示流程:

說明
在獲取鎖時,是將當前執行緒插入到cxq的頭部,而釋放鎖時,默認策略(QMode=0)是:如果EntryList為空,則將cxq中的元素按原有順序插入到EntryList,并喚醒第一個執行緒,也就是當EntryList為空時,是后來的執行緒先獲取鎖,_EntryList不為空,直接從_EntryList中喚醒執行緒,
示例演示:
1.情況1,三個執行緒ABC,分別去獲取鎖,順序為A,B,C,如果A業務時間較長,則BC都應該進入到_cxq中(FILO堆疊結構)【C,B】,由于_EntryList為空,則將cxq中的元素按原有順序插入到EntryList【B,C】,此時C先獲取鎖,結果為:
A get lock
A release lock
C get lock
C release lock
B get lock
B release lock
2.情況2,三個執行緒ABC,分別去獲取鎖,順序為A,B,C,如果A業務時間較短,進入等待狀態,進入_WaitSet中等待,則B進入時_cxq和_EntryList為空,B直接獲取鎖,執行業務時間較長,且C進入到_cxq中,而A也從_WaitSet中滿足條件進入到了_EntryList中,當B釋放鎖時,應該在_EntryList中的A先獲取鎖,當_EntryList為空時,將_cxq中的C轉入到_EntryList,等A釋放后,C才能獲取鎖,結果為:
A get lock
B get lock
B release lock
A release lock
C get lock
C release lock
3.示例代碼展示:
public class SyncQModeDemo { public static void main(String[] args) throws InterruptedException { SyncQModeDemo demo = new SyncQModeDemo(); demo.startThreadA(); //控制執行緒執行時間 Thread.sleep(100); demo.startThreadB(); Thread.sleep(100); demo.startThreadC(); } final Object lock = new Object(); public void startThreadA() { new Thread(() -> { synchronized (lock) { log.debug("A get lock"); try { Thread.sleep(300); //對應情況1,模擬業務時間 //lock.wait(300); //對應情況2 } catch (InterruptedException e) { e.printStackTrace(); } log.debug("A release lock"); } }, "thread-A").start(); } public void startThreadB() { new Thread(() -> { synchronized (lock) { try { log.debug("B get lock"); Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("B release lock"); } }, "thread-B").start(); } public void startThreadC() { new Thread(() -> { synchronized (lock) { log.debug("C get lock"); } }, "thread-C").start(); } }
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/508970.html
標籤:其他
