鎖機制初探(五)Moniter的實現原理

深入理解多線程(一)——Synchronized的實現原理中介紹過關於Synchronize的實現原理,無論是同步方法還是同步代碼塊,無論是ACC_SYNCHRONIZED還是monitorentermonitorexit都是基於Monitor實現的,那麼這篇來介紹下什麼是Monitor

操作系統中的管程

如果你在大學學習過操作系統,你可能還記得管程(monitors)在操作系統中是很重要的概念。同樣Monitor在java同步機制中也有使用。

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

Java線程同步相關的Moniter

在多線程訪問共享資源的時候,經常會帶來可見性和原子性的安全問題。爲了解決這類線程安全的問題,Java提供了同步機制、互斥鎖機制,這個機制保證了在同一時刻只有一個線程能訪問共享資源。這個機制的保障來源於監視鎖Monitor,每個對象都擁有自己的監視鎖Monitor。

先來舉個例子,然後我們在上源碼。我們可以把監視器理解爲包含一個特殊的房間的建築物,這個特殊房間同一時刻只能有一個客人(線程)。這個房間中包含了一些數據和代碼。

如果一個顧客想要進入這個特殊的房間,他首先需要在走廊(Entry Set)排隊等待。調度器將基於某個標準(比如 FIFO)來選擇排隊的客戶進入房間。如果,因爲某些原因,該客戶客戶暫時因爲其他事情無法脫身(線程被掛起),那麼他將被送到另外一間專門用來等待的房間(Wait Set),這個房間的可以可以在稍後再次進入那件特殊的房間。如上面所說,這個建築屋中一共有三個場所。

 

總之,監視器是一個用來監視這些線程進入特殊的房間的。他的義務是保證(同一時間)只有一個線程可以訪問被保護的數據和代碼。

Monitor其實是一種同步工具,也可以說是一種同步機制,它通常被描述爲一個對象,主要特點是:

對象的所有方法都被“互斥”的執行。好比一個Monitor只有一個運行“許可”,任一個線程進入任何一個方法都需要獲得這個“許可”,離開時把許可歸還。

通常提供singal機制:允許正持有“許可”的線程暫時放棄“許可”,等待某個謂詞成真(條件變量),而條件成立後,當前進程可以“通知”正在等待這個條件變量的線程,讓他可以重新去獲得運行許可。

監視器的實現

在Java虛擬機(HotSpot)中,Monitor是基於C++實現的,由ObjectMonitor實現的,其主要數據結構如下:

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

源碼地址:objectMonitor.hpp

ObjectMonitor中有幾個關鍵屬性:

_owner:指向持有ObjectMonitor對象的線程

_WaitSet:存放處於wait狀態的線程隊列

_EntryList:存放處於等待鎖block狀態的線程隊列

_recursions:鎖的重入次數

_count:用來記錄該線程獲取鎖的次數

當多個線程同時訪問一段同步代碼時,首先會進入_EntryList隊列中,當某個線程獲取到對象的monitor後進入_Owner區域並把monitor中的_owner變量設置爲當前線程,同時monitor中的計數器_count加1。即獲得對象鎖。

若持有monitor的線程調用wait()方法,將釋放當前持有的monitor,_owner變量恢復爲null_count自減1,同時該線程進入_WaitSet集合中等待被喚醒。若當前線程執行完畢也將釋放monitor(鎖)並復位變量的值,以便其他線程進入獲取monitor(鎖)。如下圖所示

 

 

 

ObjectMonitor類中提供了幾個方法:

獲得鎖

 

 

 

 

釋放鎖


 

 

 

 

除了enter和exit方法以外,objectMonitor.cpp中還有

void      wait(jlong millis, bool interruptable, TRAPS);
void      notify(TRAPS);
void      notifyAll(TRAPS);

等方法。

總結

上面介紹的就是HotSpot虛擬機中Moniter的的加鎖以及解鎖的原理。

通過這篇文章我們知道了sychronized加鎖的時候,會調用objectMonitor的enter方法,解鎖的時候會調用exit方法。事實上,只有在JDK1.6之前,synchronized的實現纔會直接調用ObjectMonitor的enterexit,這種鎖被稱之爲重量級鎖。爲什麼說這種方式操作鎖很重呢?

  • Java的線程是映射到操作系統原生線程之上的,如果要阻塞或喚醒一個線程就需要操作系統的幫忙,這就要從用戶態轉換到核心態,因此狀態轉換需要花費很多的處理器時間,對於代碼簡單的同步塊(如被synchronized修飾的get 或set方法)狀態轉換消耗的時間有可能比用戶代碼執行的時間還要長,所以說synchronized是java語言中一個重量級的操縱。

所以,在JDK1.6中出現對鎖進行了很多的優化,進而出現輕量級鎖偏向鎖鎖消除適應性自旋鎖鎖粗化(自旋鎖在1.4就有 只不過默認的是關閉的,jdk1.6是默認開啓的),這些操作都是爲了在線程之間更高效的共享數據 ,解決競爭問題。後面的文章會繼續介紹這幾種鎖以及他們之間的關係。

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