點選上方 菜鳥飛呀飛
或者掃描下方二維碼,即可關注微信公衆号。
文章目錄
-
- 1. 定義
- 2. MESA模型
-
- 2.1 互斥
- 2.2 同步
- 3. synchronized
- 4. Lock與Condition
- 5. 總結
本文主要參考了極客時間上王寶令老師的《Java并發程式設計實戰》,裡面大部分内容來自這門課程,本文中的幾張圖也是參考課程裡面的圖畫的。個人覺得這門課程比較基礎,相對而言比較簡單,老師講解得也十厘清晰,适合并發程式設計的入門,有需要的朋友可以點選文末的二維碼連接配接去了解。
1. 定義
- Monitor在英語中直譯是螢幕的意思,但是在作業系統中通常被翻譯為管程,是用來實作并發的一種技術,它解決了并發程式設計中的兩大核心問題:互斥與同步。是以管程的定義是:用來管理共享變量以及對共享變量操作的過程。
- 曆史上出現過三種管程模型,MESA模型、Hasen模型、Hoare模型。而在Java中,管程的實作是根據MESA模型實作的。
2. MESA模型
管程實作了并發程式設計領域的兩大核心問題:互斥與同步,那麼MESA模型是如何來實作互斥與同步的呢?
2.1 互斥
- 管程通過對共享變量以及對操作共享變量方法的進行了封裝,在同一時刻,隻有一個線程進入到管程中,這樣就保證了隻有一個線程來操作共享變量,實作了互斥的功能。
- 如上圖中,管程X對共享變量count,以及對操作共享變量count的兩個方法遞增:increment()和遞減:decrement()進行了封裝,要想通路共享變量count,隻能通過increment()和decrement()。由于管程保證了同時隻允許一個線程進入,是以保證了increment()和decrement()的互斥性。
2.2 同步
- 如下圖的管程模型圖中,方框代表管程對共享變量以及操作共享變量方法的封裝,在入口處有一個等待隊列,當有多個線程試圖進入管程時,管程隻允許一個線程進入,其他的線程進入到等待隊列中。
- 在管程中引入了條件變量的概念,每個條件變量都對應一個等待隊列。如圖中的條件變量A和B分别對應一個等待隊列。
-
條件變量和等待隊列的作用就是為了實作線程之間的同步問題。可以結合下面的例子了解。
假設有如下場景,對于共享變量count,初始值為0,我們需要對它進行遞增或者遞減操作,但是在進行操作時,需要滿足如下條件,進行遞增時,count的值不能大于等于10,進行遞減時,count的值需要大于0。
- 假設線程T1要對共享變量進行遞減操作,由于此時count值為0,是以不能進行遞減操作,這個時候就應該讓線程T1進行等待。去哪兒等待呢?去條件變量對應的等待隊列裡面進行等待。是以此時需要調用
這個條件變量對象的wait()方法,這樣線程T1就進入到count>0
這個條件變量的等待隊列中等待。count>0
- 再假設線程T2要對共享變量進行遞增操作,由于此時count=0,滿足
這個條件,是以T2執行遞增操作,操作之後count變為1,那麼此時對于條件變量count值不能大于等于10
對于線程T1來說已經成立了,是以這個時候T2需要通知T1條件滿足了。那麼如何通知呢?通過調用count>0
這個條件變量對象的notify()或者notifyAll()方法。通知完成後,T1線程會從條件變量的等待隊列中出來,但是此時T1不會立馬執行,而是需要重新進入到管程入口處的等待隊列中。count>0
- 可以結合如下代碼了解。在代碼中使用了java.util.concurrent包下的類Lock和Condition,await()方法和wait()方法作用是一樣的,signalAll()和notifyAll()作用是一樣的。
public class Count {
volatile int count = 0;
final Lock lock = new ReentrantLock();
// 小于10條件變量
final Condition lessThanTen = lock.newCondition();
// 大于0條件變量
final Condition moreThanZero = lock.newCondition();
void increment(){
lock.lock();
try{
while(!count小于10){
// 小于10這個條件變量不滿足,調用await()方法進入到條件變量的等待隊列中
lessThanTen.await();
}
// 執行遞增操作
// 遞增完成以後,通知大于0這個條件等待隊列中的線程
moreThanZero.signalAll();
}finally {
lock.unlock();
}
}
void decrement(){
lock.lock();
try{
while(!count大于0){
// 大于0這個條件變量不滿足,調用await()方法進入到條件變量的等待隊列中
moreThanZero.await();
}
// 執行遞減操作
// 執行遞減操作以後,通知小于10這個條件等待隊列中的線程
lessThanTen.signalAll();
}finally{
lock.unlock();
}
}
}
- 通過對上面MESA管程模型的介紹,到這裡相信你對管程有了一定的認識。那管程在Java中到底是如何實作的呢?
3. synchronized
- Java語言中為我們提供了synchronized關鍵字來實作鎖。synchronized關鍵詞實作的鎖是隐式鎖,為什麼稱之為隐式鎖呢?是因為在編譯器編譯Java檔案時,當碰到synchronized時,會自動加上加鎖指令和解鎖指令,即monitorenter和monitorexit,這一步對于開發人員來說是透明的,是以說它是隐式鎖。它的底層實作就是通過管程實作的,前面我們介紹的管程模型中,可以支援多個條件變量,但是synchronized的管程實作隻支援一個條件變量。它的管程示意圖如下:
- 那麼在虛拟機中,是如何實作管程的呢?那就是ObjectMonitor這個對象了。在openJDK的源碼中,有這樣兩個源檔案:objectMonitor.cpp和objectMonitor.hpp。前者的檔案中實作了鎖的擷取、釋放等方法,後者定義了前者需要的頭檔案。在objectMonitor.hpp檔案中定義了這樣一個結構:
ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
// 條件等待隊列
_WaitSet = NULL;
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
// 入口等待隊列
_EntryList = NULL ;
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
- 在ObjectMonitor這個結構中,有兩個重要的屬性:_WaitSet和_EntryList,這兩個屬性分别對應管程中條件等待隊列和入口等待隊列。
4. Lock與Condition
Lock和Condition是java.util.concurrent包下的類,與synchronized關鍵字實作鎖的原理不一樣,Lock是在Java層面實作的,而synchronized是通過JVM虛拟機實作的鎖。
- Lock和Condition也是通過管程模型來實作鎖的。其中Lock是是用來實作互斥的,Condition是用來實作同步的。
- 與synchronized不同的是,Lock與Condition實作的管程,支援多條件變量,而synchronized的管程實作隻支援單個條件變量。
5. 總結
- 本文主要介紹了管程的模型以及管程是如何來實作并發的,
- 本文結合Java中synchronized和Lock介紹了管程的實作。
本文的内容更偏于個人的學習筆記,很多地方講解得不是很詳細,如果想了解更加詳細的,可以去看看極客時間上《Java并發程式設計實戰》這門課程,下方是課程的二維碼連結。