在Java 1.5以前,Java語言提供的惟一併發語言就是管程,Java 1.5以後提供的SDK併發包也是以管程爲基礎的。除了Java以外,C/C++、C#等高級語言也都是支持管程的。html
那麼什麼是管程呢?
見名知意,是指管理共享變量以及對共享變量操做的過程,讓它們支持併發。翻譯成Java領域的語言,就是管理類的狀態變量,讓這個類是線程安全的。java
synchronized關鍵字和wait()、notify()、notifyAll()這三個方法是Java中實現管程技術的組成部分。記得學習操做系統時,在線程一塊還有信號量機制,管程在功能上和信號量及PV操做相似,屬於一種進程同步互斥工具。Java選擇管程來實現併發主要仍是由於實現管程比較容易。編程
管程對應的英文是Monitor,直譯爲「監視器」,而操做系統領域通常翻譯爲「管程」。安全
在管程的發展史上,前後出現過三種不一樣的管程模型,分別是Hasen模型、Hoare模型和MESA模型。如今正在普遍使用的是MESA模型。下面咱們便介紹MESA模型。併發
管程中引入了條件變量的概念,並且每一個條件變量都對應有一個等待隊列。條件變量和等待隊列的做用是解決線程之間的同步問題。工具
咱們來看一個例子來理解這個模型。多個線程對一個共享隊列進行操做。學習
假設線程T1要執行出隊操做,可是這個操做要執行成功的前提是隊列不能爲空。這個隊列不能爲空就是管程裏的條件變量。如果線程T1進入管程後發現隊列是空的,那它就須要在「隊列不空」這個條件變量的等待隊列中等待。
經過調用wait()
實現。如果用對象A表明「隊列不空」這個條件,那麼線程T1須要調用A.wait()
,來將本身阻塞。
在線程T1進入條件變量的等待隊列後,是容許其餘線程進入管程的。操作系統
再假設以後另一個線程T2執行了入隊操做,入隊操做成功以後,「隊列不空」這個條件對於線程T1來講已經知足了,此時線程T2要通知線程T1,告訴它調用須要的條件已經知足了。
那麼線程T2怎麼通知線程T1?線程T2調用A.notify()
來通知A等待隊列中的一個線程,此時這個線程裏面只有T1,因此notify喚醒的就是線程T1,若是當這個條件變量的等待隊列不止T1一個線程,咱們就須要使用notifyAll()。
當線程T1獲得通知後,會從等待隊列中出來,從新進入到入口等待隊列中。線程
使用代碼說明就以下:(代碼來自參考[1])
注意,await()
和前面的wait()
的語義是同樣的;signal()
和前面的notify()
語義是同樣的(沒有提到的signalAll()
和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(); } } }
對於MESA管程來講,有一個編程範式:
while(條件不知足) { wait(); }
咱們在前面介紹等待-通知機制時就提到過這種範式。這個範式能夠解決「條件曾將知足過」這個問題。喚醒的時間和獲取到鎖繼續執行的時間是不一致的,被喚醒的線程再次執行時可能條件又不知足了,因此循環檢驗條件。
MESA模型的wait()方法還有一個超時參數,爲了不線程進入等待隊列永久阻塞。
知足如下三個條件時,可使用notify(),其他狀況儘可能使用notifyAll():
Hasen模型、Hoare模型和MESA模型的一個核心區別是當條件知足後,如何通知相關線程。
管程要求同一時刻只容許一個線程執行,那當線程T2的操做使得線程T1等待的條件知足時,T1和T2究竟誰能夠執行呢?
Java 參考了 MESA 模型,語言內置的管程(synchronized)對 MESA 模型進行了精簡。MESA 模型中,條件變量能夠有多個,Java 語言內置的管程裏只有一個條件變量。模型以下圖所示。(圖來自參考[1])
Java 內置的管程方案(synchronized)使用簡單,synchronized 關鍵字修飾的代碼塊,在編譯期會自動生成相關加鎖和解鎖的代碼,可是僅支持一個條件變量;而 Java SDK 併發包實現的管程支持多個條件變量,不過併發包裏的鎖,須要咱們本身進行加鎖和解鎖操做。
開始原本打算不寫這篇學習筆記的,可是思考了一下,Java併發實現本就是源於操做系統中的管程,既然要好好介紹Java併發那麼它的來源也應該要好好介紹一下。在學習一個知識的時候,其背後的理論也要好好掌握。
參考: [1]極客時間專欄王寶令《Java併發編程實戰》