天天看點

臨界區、管程、信号量介紹前言臨界區信号量管程總結

前言

臨界區、管程、信号量是作業系統對并發程式設計支援的三個概念。它們并不是屬于java,而是屬于作業系統(目前我知道 linux有這三個概念,我覺得windows也應該有隻是不确定,否則怎麼會支援多核CPU呢?)。

臨界區

臨界區是線程需要互斥執行的一段代碼。臨界區的資源是線程共享的,但是執行的傳回結果是不确定的。 舉個例子:linux系統中的fork()指令會開啟一個子線程,每個子線程都應該去調用父線程的方法傳回一個PID。假設這個方法是:

NEW_PID = next_pid++
           

上面的next_pid就是臨界資源,是多個線程共享的, 這段代碼不是原子操作,但是一定要是互斥的。 臨界區隻是一個概念,具體實作要看程式設計語言還作業系統。

信号量

信号量也是一個程式設計概念,基本由一個整數變量,和兩個原子操作組成。這兩個原子操作對這個整數變量對行增加和删除。成功才執行後面的代碼,失敗則阻塞。 具體代碼實作可以看我的另一篇部落格。

https://blog.csdn.net/u011296165/article/details/80249229

管程

管程對應的英文是Monitor。管程的定義是管理共享變量以及對共享變量的操作過程,讓它們支援并發。相對應的java語意是管理java類的成員變量和成員方法,讓這個類是線程安全的。

管程是程式設計語言提供的一種抽象資料結構,它有兩個特點

  1. 互斥通路,即任一時刻隻有一個線程在執行管程代碼;
  2. 正在管程内的線程可以放棄對管程的控制權;

    條件變量(condition variable)是管程内部的實作機制,每個條件變量都代表一種等待的原因,也對應一個等待隊列。條件變量有兩個操作:wait和signal。這兩個條件變量的操作,是某種情況下等待和在某種情況下執行。

MESA模型----java管程使用的模型

管程實作互斥

管程實作互斥就是将共享變量和對共享變量操作的方法封閉起來。如下圖,管程X将共享變量queue這個隊列和相關操作入隊enq()和deq()都封裝起來。線程A和線程B如果想要通路共享變量queue()隊列,就要使用enq()和deq()方法,enq()和deq()保持了互斥性,這樣來保證線程安全。

管程實作同步

MESA的模型圖

臨界區、管程、信号量介紹前言臨界區信号量管程總結

圖中最大的方框就代表封裝的意思, 上方開口就是管程的入口, 入口旁邊有一個等待隊列。一個管程隻能有一個線程去執行,其他線程就進入到這個等待隊列。 就像是去醫院看醫生,一個醫生隻能同時給一個病人看病,其他拿到号的病人都會在門外等着。

管程裡面還有一個條件變量的概念,管程裡面還有一個條件變量的概念,管程裡面還有一個條件變量的概念這一定非常重要。 後面的東西都是使用了這個條件變量。每一個條件變量都有自己的等待隊列,就如圖上的條件變量A和條件變量B有兩個等待隊列。

現在對他的執行過程做一個說明。我們管程中通常兩個方法,方法X代表出隊操作,方法Y代表入隊操作。我們的共享變量V就是隊列。我們的假設線程T1在執行方法X出隊操作,那麼在執行這個出隊操作之前是不是應用有一個前置條件,判斷這個共享變量V隊列不能為空。 而這個前置條件就是條件變量。條件變量的等待隊列就是無法擷取條件的時候,條件變量執行了wait()方法,将線程放入到了這個條件變量的等待隊列中了。

舉個生活中的例子:小明去醫院看醫生,第一步、進醫院的就是去取号,然後去對應醫生辦公室門口等待叫号。這一步對應的就管程入口等待對列。 第二步 叫到号之後醫生讓小明去拍X光片,他就去抽血處排隊抽血。這一步對應的就是條件變量等待對列。第三步 抽完血之後,小明又要去醫生辦公室門口等待叫号,這一步對應的就是當條件變量符合執行條件就從條件變量隊列中釋放,但并不是馬上去執行,而是去管程入口隊列中等待。第四步,小明進入醫生辦公室看醫生,醫生讓小明在去驗上血。小明就要去驗血處等待。這一步對應的就是當線程第二次進入管程中,發現條件變量又不符合了,又要去重新進入條件變量隊列中重新等待條件符合要求。 當符合要求後在次進入管程入口等待隊列,直到進入管程後條件符合要求去執行方法完成。

這裡可能有點繞,還請多看幾遍。

下面用代碼做一下示範。

假定對象A代表“隊列不空”這個條件,那麼線程進入管程需要判斷隊列是否為空,如果為空則調用 A.wait()方法。同理當“條件 不空”這個條件滿足時,線程T2需要調用 A.notify()來通知A等待隊列中的一個線程。 也可以調用 notifyAll()這個方法。

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();
    }  
  }
}

           
  1. 對于入隊操作,如果隊列已滿。就需要等待直接隊列不滿,是以這就用notFull.await()。
  2. 對于出隊操作,如果隊列為空,就需要等待直接到隊不空,是以這就用notEmpty.await()。
  3. 如果入隊成功,那麼隊列就不空,就需要通知隊列不空的等待隊列。這裡就要用notEmpty.signal();
  4. 如果出隊成功,那就隊列就不滿,就需要通知隊列不滿的等待隊列。就這裡要用notFull.signal();

總結

java MESA模型

臨界區、管程、信号量介紹前言臨界區信号量管程總結

java MESA 模型中隻有一個條件變量,是使用synchronized關鍵字來實作的。而java sdk并發包中支援多個條件變量,需要開發人員手動去調用加鎖和解鎖操作。

注:

借鑒極客時間java并發程式設計和另外一篇部落格,那篇部落格因電腦突然關機找不到了,如發現請通知,我在加上連結。

繼續閱讀