Java中的監視器(monitor)是什么?


前言

如果查看Java源碼java.lang.Object,就能夠看到好多地方提到監視器(monitor),都是出現在描述線程競爭關系的時候,比如Object.notify方法和Object.wait方法。

簡要描述

監視器是一個用來保證多個線程安全訪問共享數據的機制。

監視器和鎖的區別?

嚴格地講,“監視器”和“鎖”這兩個術語表達的含義是不一樣的,但實際上很多時候兩個術語是可以互換的。

  • 鎖:是一個具有獲取和釋放語句以維持特定屬性的東西,比如:互斥鎖,讀寫鎖
  • 監視器:是一個機制,或者說是一個概念,用來保證在任何時候只有一個線程能夠執行給定區塊的代碼。“監視器”可以通過“鎖”來實現,但“監視器”的含義大於“鎖”的含義。

Java中的監視器支持兩種線程同步方式:互斥和協作。

  • 互斥:在Java虛擬機中,線程互斥是通過對象鎖完成的。互斥可以使線程獨立使用共享的數據,而不被其它線程干擾。
  • 協作:在Java虛擬機中,線程協作是通過Object類中的wait和notify方法完成的。協作可以使多個線程為了同一個目標一起工作。

Java中的監視器

可以把監視器理解為一個建築,這個建築里有一個特殊的房間,這個房間同一時刻只能被一個線程所占有。這個房間里有一些數據。一個線程從進入該房間到離開該房間,可以全程獨占享有該房間的所有數據。進入該建築叫做進入監視器(entering the monitor),進入該房間叫做獲得監視器(acquiring the monitor),獨自占有該房間叫做擁有監視器(owning the monitor),離開該房間叫做釋放監視器(releasing the monitor),離開該建築叫做退出監視器(exiting the monitor)。

為了和數據打交道,一個監視器必須關聯一些代碼。一個監視器區域(一段代碼)是一個不可分割的操作,即一個線程在執行監視器區域代碼時,不允許其它線程同時執行該監視器區域代碼。監視器需要保證同一時間只有一個線程在執行它的監視區域代碼。一個線程想要進入該監視器,必須執行到該監視器區域代碼的開始處,而該線程如果想要繼續前進以執行監視器區域代碼,則需要先獲得該監視器。

當線程執行到該監視器區域時,會被放進監視器的入口區域(entry set),入口區域(entry set)相當於建築里面的走廊,這時候相當於進入監視器。

  1. 如果入口區域中沒有其它線程,並且沒有其它線程占有監視器,那么該線程就可以獲取監視器,並繼續執行監視器區域代碼。當該線程執行完監視器區域代碼,則釋放和退出監視器。
  2. 如果入口區域有其它線程,並且此時有一個線程正占有監視器,則該線程必須在入口區域等待,當占有監視器的線程退出監視器,則新來的線程必須和其它在入口區域中的線程進行競爭,競爭成功的線程才能獲取監視器。

互斥方式可以避免線程使用共享數據時被其它線程干擾,而協作方式則幫助多個線程共同完成同一個目標。

當一個線程需要某些狀態下的數據時,協作方式就變得很重要。舉個例子,如果有兩個線程,一個讀線程,一個寫線程,讀線程讀出寫線程寫入緩存的數據。如果緩存區沒有數據,則讀線程必須等待,等到寫線程寫入數據之后,讀線程才能正常工作。

這種監視器通常被叫做等待和通知監視器(a "Wait and Notify" monitor),在這種類型的監視器中,占有當前監視器的線程可以通過執行等待命令(a wait command)來掛起自己,進入等待區(wait set),進入等待區的線程一直等到該監視器的其它線程執行通知命令(a notify command)才重新被激活。當該監視器里的一個線程執行了通知命令,執行通知命令的線程會繼續占有該監視器,直到該線程主動釋放該監視器(完成執行監視器區域代碼或執行了等待命令進入了等待區)。當發出通知的線程釋放了該監視器,在等待區等待的線程才會被復活,重新將會獲得監視器。

A java monitor

圖中有3個矩形,中間的大矩形僅包含一個線程(占有監視器的線程),左邊的小矩形是入口區域(entry set),右邊的小矩形是等待區(wait set)。5個標了序號的小門,代表了線程與監視器交互的過程。

整個過程是這樣的:

  1. 當一個線程執行到監視器區域代碼開始處時,穿過1號門進入監視器入口區域。
  2. 如果入口區域中沒有其它線程,並且沒有其它線程占有監視器,那么該線程就立即穿過2號門以占有監視器,這時候該線程可以繼續執行監視器區域代碼。如果此時監視器被其它線程占有,該線程則必須在入口區域等待,也因此被阻塞無法繼續執行監視器區域代碼。
  3. 如果占有監視器的線程發出等待命令,則穿過3號門釋放監視器並進入等待區。
  4. 如果等待區的線程競爭成功,成功占有監視器,則通過4號門占有監視器,並繼續執行監視器區域代碼。
  5. 當占有監視器的線程執行完監視器區域代碼,則穿過5號門釋放並退出監視器。

注意

  1. 如果占有監視器的線程未發出過通知命令,則釋放並退出監視器后,只有入口區域的線程會競爭獲取監視器
  2. 如果占有監視器的線程發出過通知命令,則釋放並退出監視器后,入口區域的線程會同一個或多個等待區的線程共同競爭獲取監視器
  3. Java虛擬機提供了2個通知命令:notify()和notifyAll()。其中notify()方法喚醒任意一個等待區中的線程,notifyAll()方法喚醒等待區中的全部線程。
  4. Java虛擬機從下一個等待區或入口區選擇下一個占有監視器的線程的算法由虛擬機的具體實現決定。

參考連接


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM