天天看點

管程:并發程式設計的基石

點選上方

菜鳥飛呀飛

或者掃描下方二維碼,即可關注微信公衆号。
管程:并發程式設計的基石

文章目錄

    • 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進行等待。去哪兒等待呢?去條件變量對應的等待隊列裡面進行等待。是以此時需要調用

    count>0

    這個條件變量對象的wait()方法,這樣線程T1就進入到

    count>0

    這個條件變量的等待隊列中等待。
  • 再假設線程T2要對共享變量進行遞增操作,由于此時count=0,滿足

    count值不能大于等于10

    這個條件,是以T2執行遞增操作,操作之後count變為1,那麼此時對于條件變量

    count>0

    對于線程T1來說已經成立了,是以這個時候T2需要通知T1條件滿足了。那麼如何通知呢?通過調用

    count>0

    這個條件變量對象的notify()或者notifyAll()方法。通知完成後,T1線程會從條件變量的等待隊列中出來,但是此時T1不會立馬執行,而是需要重新進入到管程入口處的等待隊列中。
  • 可以結合如下代碼了解。在代碼中使用了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并發程式設計實戰》這門課程,下方是課程的二維碼連結。
管程:并發程式設計的基石