日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java monitor

發布時間:2023/12/18 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java monitor 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

【個人筆記搬運到博客系列】

一、monitor概念

在操作系統中,存在著semaphore和mutex,為了更好的編寫并發程序,在mutex和semaphore基礎上,提出了更高層次的同步原語,實際上,monitor屬于編程語言的范疇,具體的實現模式,不同的編程語言都有可能不一樣,C語言不支持monitor,而java支持monitor機制。

一個重要特點是,在同一時間,只有一個線程/進程能進入monitor所定義的臨界區,這使得monitor能夠實現互斥的效果。無法進入monitor的臨界區的進程/線程,應該被阻塞,并且在適當的時候被喚醒。顯然,monitor作為一個同步工具,也應該提供這樣管理線程/進程的機制。

monitor這個機制之所以被稱為:更高級的原語,它不可避免的需要對外屏蔽這些機制并且在內部實現這些機制

二、monitor基本元素

  • 臨界區
  • monitor對象和鎖
  • 條件變量,以及定義在monitor對象上的wait,notify操作

使用monitor主要是為了互斥進入臨界區,為了能夠阻塞無法進入臨界區的進程,線程,需要一個monitor object來協助,這個object內部會有相應的數據結構,例如列表,用來保存被阻塞的線程;同時由于monitor機制本質是基于mutex原語的,所以object必須維護一個基于mutex的鎖。

此外,為了在適當的時候能夠阻塞和喚醒 進程/線程,還需要引入一個條件變量,這個條件變量用來決定什么時候是“適當的時候”,這個條件可以來自程序代碼的邏輯,也可以是在 monitor object 的內部,總而言之,程序員對條件變量的定義有很大的自主性。不過,由于 monitor object 內部采用了數據結構來保存被阻塞的隊列,因此它也必須對外提供兩個 API 來讓線程進入阻塞狀態以及之后被喚醒,分別是 wait 和 notify。

三、monitor在java中的實現

臨界區:被synchronized關鍵字修飾的方法,代碼塊,就是monitor機制的臨界區;

monitor object:在上述synchronized關鍵字被使用時,往往需要指定一個對象與之關聯,例如synchronized(this),總之,synchronized需要管理一個對象,這個對象就是monitor object

Java 對象存儲在內存中,分別分為三個部分,即對象頭、實例數據(對象體)和對齊填充,而在其對象頭中,保存了鎖標識;同時,java.lang.Object 類定義了 wait(),notify(),notifyAll() 方法,這些方法的具體實現,依賴于一個叫 ObjectMonitor 模式的實現,這是 JVM 內部基于 C++ 實現的一套機制,基本原理如下所示:

以下以 Java 的 monitor 為例子,來講解 monitor 在 Java 中的實現方式:

在Java中,一個對象對應了一個momitor對象,而synchronized關鍵字也需要關聯一個對象,這個對象需要天生就支持monitor,所以在Java中,可以就是Java 中的 java.lang.Object 類,便是滿足這個要求的對象,任何一個 Java 對象都可以作為 monitor 機制的 monitor object。這也就是wait(),notify(),notifyAll(),是在Object類中的原因。

四、monitor詳解

4.1、線程狀態

 Java中線程中狀態可分為五種:New(新建狀態),Runnable(就緒狀態),Running(運行狀態),Blocked(阻塞狀態),Dead(死亡狀態)。

New:新建狀態,當線程創建完成時為新建狀態,即new Thread(...),還沒有調用start方法時,線程處于新建狀態。

Runnable:就緒狀態,當調用線程的的start()方法后,線程進入就緒狀態,等待CPU資源。處于就緒狀態的線程由Java運行時系統的線程調度程序(thread scheduler)來調度。

Running:運行狀態,就緒狀態的線程獲取到CPU執行權以后進入運行狀態,開始執行run()方法

Blocked:阻塞狀態,線程沒有執行完,由于某種原因(如,I/O操作等)讓出CPU執行權,自身進入阻塞狀態。

Dead:死亡狀態,線程執行完成或者執行過程中出現異常,線程就會進入死亡狀態。

這五種狀態之間的轉換關系如下圖所示:

4.2、wait/notify/notifyAll方法的使用

【1】wait方法

有三種版本:

(1)wait(),作用是將當前運行的線程掛起(即讓其進入阻塞狀態),直到notify或notifyAll方法來喚醒線程;

(2)wait(long timeout),該方法與wait()方法類似,唯一的區別就是在指定時間內,如果沒有notify或notifAll方法的喚醒,也會自動喚醒

(3)wait(long timeout,long nanos),在于更精確的控制調度時間;

核心wait方法的使用必須在同步的范圍內,否則就會拋出IllegalMonitorStateException異常,wait方法的作用就是阻塞當前線程等待notify/notifyAll方法的喚醒,或等待超時后自動喚醒。

public class WaitTest {public void testWait(){System.out.println("Start-----");try {wait(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("End-------");}public static void main(String[] args) {final WaitTest test = new WaitTest();new Thread(new Runnable() {@Overridepublic void run() {test.testWait();}}).start();} }

如果按照以上代碼這么寫,會拋出IllegalMonitorStateException異常,意思是:線程試圖等待對象的監視器或者試圖通知其他正在等待對象監視器的線程,但本身沒有對應的監視器的所有權。

由于wait方法是一個本地方法,其底層是通過一個叫做監視器鎖的對象monitor來完成的。所以上面之所以會拋出異常,是因為在調用wait方式時沒有獲取到monitor對象的所有權,那如何獲取monitor對象所有權?Java中只能通過Synchronized關鍵字來完成,修改上述代碼,增加Synchronized關鍵字,即可運行:

public class WaitTest {public synchronized void testWait(){//增加Synchronized關鍵字System.out.println("Start-----");try {wait(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("End-------");}public static void main(String[] args) {final WaitTest test = new WaitTest();new Thread(new Runnable() {@Overridepublic void run() {test.testWait();}}).start();} }

通過上述代碼示例,可以看出,wait方法的使用必須在同步的范圍內,否則就會拋出IllegalMonitorStateException異常,wait方法的作用就是阻塞當前線程等待notify/notifyAll方法的喚醒,或等待超時后自動喚醒。

【2】notify/notifyall

有了對wait方法原理的理解,notify方法和notifyAll方法就很容易理解了。既然wait方式是通過對象的monitor對象來實現的,所以只要在同一對象上去調用notify/notifyAll方法,就可以喚醒對應對象monitor上等待的線程了。notify和notifyAll的區別在于前者只能喚醒monitor上的一個線程,對其他線程沒有影響,而notifyAll則喚醒所有的線程。

有兩點點需要注意:

(1)調用wait方法后,線程是會釋放對monitor對象的所有權的。

(2)一個通過wait方法阻塞的線程,必須同時滿足以下兩個條件才能被真正執行:

    線程需要被喚醒(超時喚醒或調用notify/notifyll)。
    線程喚醒后需要競爭到鎖(monitor)。

4.3、sleep/yield/join方法使用:

再來看另外一組線程間協作的方法。這組方法跟上面方法的最明顯區別是:這幾個方法都位于Thread類中,而上面三個方法都位于Object類中。

【1】sleep方法

sleep方法的作用是讓當前線程暫停指定的時間(毫秒),sleep方法是最簡單的方法,比較容易理解。

唯一需要注意的是其與wait方法的區別最簡單的區別是,wait方法依賴于同步,而sleep方法可以直接調用。而更深層次的區別在于sleep方法只是暫時讓出CPU的執行權,并不釋放鎖。而wait方法則需要釋放鎖

通過sleep方法實現的暫停,程序是順序進入同步塊的,只有當上一個線程執行完成的時候,下一個線程才能進入同步方法,sleep暫停期間一直持有monitor對象鎖,其他線程是不能進入的。而wait方法則不同,當調用wait方法后,當前線程會釋放持有的monitor對象鎖,因此,其他線程還可以進入到同步方法,線程被喚醒后,需要競爭鎖,獲取到鎖之后再繼續執行。

【2】yield方法
yield方法的作用是暫停當前線程,以便其他線程有機會執行,不過不能指定暫停的時間,并且也不能保證當前線程馬上停止。yield方法只是將Running狀態轉變為Runnable狀態。

不過請注意:這種交替并不一定能得到保證,而且源碼中也對這個問題進行了說明,意思總結是:調度器可能會忽略該方法;使用的時候要仔細分析和測試,確保能達到預期的效果;很少有場景要用到該方法,主要使用的地方是調試和測試。

【3】join方法

有三個版本:

void join()?? ?
void join(long millis)
void join(long millis, int nanos)

join方法的作用是:父線程等待子線程執行完成后再執行,換句話說就是將異步執行的線程合并為同步的線程。JDK中提供三個版本的join方法,其實現與wait方法類似,join()方法實際上執行的join(0),而join(long millis, int nanos)也與wait(long millis, int nanos)的實現方式一致,暫時對納秒的支持也是不完整的。

重點關注一下join(long millis)方法的實現,可以看出join方法就是通過wait方法來將線程的阻塞,如果join的線程還在執行,則將當前線程阻塞起來,直到join的線程執行完成,當前線程才能執行。不過有一點需要注意,這里的join只調用了wait方法,卻沒有對應的notify方法,原因是Thread的start方法中做了相應的處理,所以當join的線程執行完成以后,會自動喚醒主線程繼續往下執行

在沒有使用join方法之間,線程是并發執行的,而使用join方法后,所有線程是順序執行的。

總結:

以上這部分詳細講解了wait/notify/notifyAll和sleep/yield/join方法。

最后回答一個問題:wait/notify/notifyAll方法的作用是實現線程間的協作,那為什么這三個方法不是位于Thread類中,而是位于Object類中?

位于Object中,也就相當于所有類都包含這三個方法(因為Java中所有的類都繼承自Object類)。要回答這個問題,還是得回過來看wait方法的實現原理,大家需要明白的是,wait等待的到底是什么東西?如果對上面內容理解的比較好的話,我相信大家應該很容易知道wait等待其實是對象monitor,由于Java中的每一個對象都有一個內置的monitor對象,自然所有的類都理應有wait/notify方法。

4.4、monitor詳解:

Java平臺中,每個對象都有一個唯一與之對應的內部鎖(Monitor)。Java虛擬機會為每個對象維護兩個“隊列”(姑且稱之為“隊列”,盡管它不一定符合數據結構上隊列的“先進先出”原則):一個叫Entry Set(入口集),另外一個叫Wait Set(等待集)。對于任意的對象objectX,objectX的Entry Set用于存儲等待獲取objectX對應的內部鎖的所有線程。objectX的Wait Set用于存儲執行了objectX.wait()/wait(long)的線程。

?

設objectX是任意一個對象,monitorX是這個對象對應的內部鎖,假設有線程A、B、C同時申請monitorX,那么由于任意一個時刻只有一個線程能夠獲得(占用/持有)這個鎖,因此除了勝出(即獲得了鎖)的線程(這里假設是B)外,其他線程(這里就是A和C)都會被暫停(線程的生命周期狀態會被調整為BLOCKED)。這些因申請鎖而落選的線程就會被存入objectX對應的Entry Set(以下記為entrySetX)之中。當monitorX被其持有線程(這里就是B)釋放時,entrySetX中的一個任意(注意是“任意”,而不一定是Entry Set中等待時間最長或者最短的)線程會被喚醒(即線程的生命周期狀態變更為RUNNABLE)。這個被喚醒的線程會與其他活躍線程(即不處于Entry Set之中,且線程的生命周期狀態為RUNNABLE的線程)再次搶占monitorX。這時,被喚醒的線程如果成功申請到monitorX,那么該線程就從entrySetX中移除。否則,被喚醒的線程仍然會停留在entrySetX,并再次被暫停,以等待下次申請鎖的機會。

?

如果有個線程執行了objectX.wait(),那么該線程就會被暫停(線程的生命周期狀態會被調整為WAITTING)并被存入objectX的Wait Set(以下記為waitSetX)之中。此時,該線程就被稱為objectX的等待線程。當其他線程執行了objectX.notify()/notifyAll()時,waitSetX中的一個(或者多個,取決于被調用的是notify還是notifyAll方法)任意(注意是“任意”,而不一定是Entry Set中等待時間最長或者最短的)等待線程會被喚醒(線程的生命周期狀態變更為RUNNABLE)。這些被喚醒的線程會與entrySetX中被喚醒的線程以及其他(可能的)活躍線程共同參與搶奪monitorX。如果其中一個被喚醒的等待線程成功申請到鎖,那么該線程就會從waitSetX中移除。否則,這些被喚醒的線程仍然停留在waitSetX中,并再次被暫停,以等待下次申請鎖的機會。

問題:我理解調用對象的 notifyAll方法后,waitSet 上的線程都會加入到 entrySet 中的吧?在一個持有鎖的線程釋放鎖后,應該只有 entrySet 隊列的線程可能獲取鎖,那這個通知是 park 來實現的嗎?是否有保證獲取鎖公平性的相關設置?

【1】從Java虛擬機性能的角度來說,Java虛擬機沒有必要在notifyAll調用之后“將Wait Set中的線程移入Entry Set”。首先,從一個“隊列”移動到另外一個“隊列”是有開銷的,其次,雖然notifyAll調用后Wait Set中的多個線程會被喚醒,但是這些被喚醒的線程極端情況下可能沒有任何一個能夠獲得鎖(比如被其他活躍線程搶先下手了)或者即便可以獲得鎖也可能不能繼續運行(比如這些等待線程所需的等待條件又再次不成立)。那么這個時候,這些等待線程仍然需要老老實實在wait set中待著。因此,如果notifyAll調用之后就將等待線程移出wait set會導致浪費(白白地進出“隊列”)。這點可以參考顯式鎖的實現:

java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(Node, int)/*** Acquires in exclusive uninterruptible mode for thread already in* queue. Used by condition wait methods as well as acquire.** @param node the node* @param arg the acquire argument* @return {@code true} if interrupted while waiting*/ final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);} }

從上面的代碼可以看出,(使用顯式鎖時)被喚醒的線程獲得鎖(tryAcquire調用返回true)之后才被從wait set中移出(setHead調用)。

【2】內部鎖僅僅支持非公平鎖調度。顯式鎖既支持公平鎖又支持非公平鎖。

LockSupport.park/upark是在jdk1.5開始引入的,顯式鎖的在實現線程的暫停和喚醒的時候會用到這個兩個方法。而內部鎖是在jdk1.5之前就已經存在的。

?

?

參考:

Java 并發編程:線程間的協作(wait/notify/sleep/yield/join):https://www.cnblogs.com/paddix/p/5381958.html
java的鎖池和等待池:https://www.cnblogs.com/tiancai/p/9371655.html

總結

以上是生活随笔為你收集整理的java monitor的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。