管程(Moniter): 併發編程的基本心法

JavaStorm 關注公衆號獲取更多併發

吃透 Syncchronized 原理 中介紹了關於 Synchronize的實現原理,無論是同步方法還是同步代碼塊,無論是ACC_SYNCHRONIZED還是monitorentermonitorexit都是基於Monitor實現的,那麼這篇來介紹下什麼是Monitor

所謂管程:指的是管理共享變量以及對共享變量的操作過程,讓它們支持併發。翻譯爲 Java 就是管理類的成員變量和成員方法,讓這個類是線程安全的。

是一種程序結構,結構內的多個子程序(對象或模塊)形成的多個工作線程互斥訪問共享資源。這些共享資源一般是硬件設備或一羣變量。管程實現了在一個時間點,最多隻有一個線程在執行管程的某個子程序。與那些通過修改數據結構實現互斥訪問的併發程序設計相比,管程實現很大程度上簡化了程序設計。 管程提供了一種機制,線程可以臨時放棄互斥訪問,等待某些條件得到滿足後,重新獲得執行權恢復它的互斥訪問。

MESA 模型

在管程的發展史上,先後出現過三種不同的管程模型,分別是:Hasen 模型、Hoare 模型和 MESA模型。其中,現在廣泛應用的是 MESA 模型,並且 Java 管程的實現參考的也是 MESA 模型。所以今天我們重點介紹一下MESA 模型。

在併發領域,有兩個核心問題:一個是互斥,一個是同步。管程就是來解決這兩個問題的。

  • 互斥:同一時刻只允許一個線程訪問共享資源。
  • 同步:線程之間如何通信、協作。

管程互斥與同步實現

它的思路很簡單,將共享變量以及對共享變量的操作統一封裝起來。如下圖所示,管程 A 將共享變量 data 和相關的操作入隊enq()、出隊deq() 封裝起來。線程 A 和線程 B想訪問共享變量 data ,只能通過調用管程提供的 enq()deq() 。當然前提是 enq()deq() 保證互斥性,只允許一個線程進入管程。是不是很有面向對象的感覺。

(管程)

在管程模型裏,共享變量和對共享變量的操作是被封裝起來的,圖中最外層的框就代表封裝的意思。框的上面只有一個入口,並且在入口旁邊還有一個入口等待隊列。當多個線程同時試圖進入管程內部時,只允許一個線程進入,其他線程則在入口等待隊列中等待。這個過程類似就醫流程的分診,只允許一個患者就診,其他患者都在門口等待。

管程裏還引入了條件變量的概念,而且每個條件變量都對應有一個等待隊列,如下圖,條件變量 A 和條件變量 B 分別都有自己的等待隊列。

管程同步

通過條件通知去喚醒等待隊列的線程競爭 鎖資源。

我們通過一段代碼說明,實現一個阻塞隊列,隊列分別有出隊與入隊,都是要先獲取互斥鎖,就像管程中的入口。

  1. 對於入隊操作,如果隊列已滿,就需要等待直到隊列不滿,所以這裏用了notFull.await();
  2. 對於出隊操作,如果隊列爲空,就需要等待直到隊列不空,所以就用了notEmpty.await();
  3. 如果入隊成功,那麼隊列就不空了,就需要通知條件變量:隊列不空notEmpty對應的等待隊列。
  4. 如果出隊成功,那就隊列就不滿了,就需要通知條件變量:隊列不滿notFull對應的等待隊列。
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();
    }  
  }
}

在這段示例代碼中,我們用了 Java 併發包裏面的 Lock 和 Condition,如果你看着吃力,也沒關係,後面我們還會詳細介紹,這個例子只是先讓你明白條件變量及其等待隊列是怎麼回事。需要注意的是:await() 和前面我們提到的 wait() 語義是一樣的;signal() 和前面我們提到的 notify() 語義是一樣的。管程通過條件隊列通信實現了同步,爲我們 Java中的併發編程提供了基本支持。

關注公衆號 JavaStorm 獲取更多併發原理

JavaStorm

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章