一題帶你徹底理解sleep()和wait()

技術小能手發表於2018-11-16

一道Java的題目:

關於sleep()和wait(),以下描述錯誤的一項是:

  • A sleep是執行緒類(Thread)的方法,wait是Object類的方法;

  • B sleep不釋放物件鎖,wait放棄物件鎖

  • C sleep暫停執行緒、但監控狀態仍然保持,結束後會自動恢復

  • D wait後進入等待鎖定池,只有針對此物件發出notify方法後獲得物件鎖進入執行狀態

關於物件鎖:

擷取網上的一段話:

所有物件都自動含有單一的鎖。

JVM負責跟蹤物件被加鎖的次數。如果一個物件被解鎖,其計數變為0。在任務(執行緒)第一次給物件加鎖的時候,計數變為1。每當這個相同的任務(執行緒)在此物件上獲得鎖時,計數會遞增。 只有首先獲得鎖的任務(執行緒)才能繼續獲取該物件上的多個鎖。 每當任務離開一個synchronized(同步)方法,計數遞減,當計數為0的時候,鎖被完全釋放,此時別的任務就可以使用此資源。

這段話令人感到迷惑,一個物件不是隻有一個鎖嗎?只有獲得這個物件的鎖才能對它進行操作,若這個物件的鎖被一個執行緒先獲得,那就其他執行緒就需要等待。那多次加鎖什麼意思,鎖不是依附於物件的嗎?

在往下的文章中,我暫且理解為一個物件有且只有一把鎖,鎖在不同執行緒間傳遞,一個執行緒可以多次獲得同一個物件的鎖。暫且不考慮一個物件上多個鎖這種方法是不是確實存在,這對下面影響不大。

關於鎖池和等待池

在Java中,每個物件都有兩個池,鎖(monitor)池和等待池

  • 鎖池 :假設執行緒A已經擁有了某個物件(注意:不是類)的鎖,而其它的執行緒想要呼叫這個物件的某個synchronized方法(或者synchronized塊),由於這些執行緒在進入物件的synchronized方法之前必須先獲得該物件的鎖的擁有權,但是該物件的鎖目前正被執行緒A擁有,所以這些執行緒就進入了該物件的鎖池中。

  • 等待池 :假設一個執行緒A呼叫了某個物件的wait()方法,執行緒A就會釋放該物件的鎖(因為wait()方法必須出現在synchronized中,這樣自然在執行wait()方法之前執行緒A就已經擁有了該物件的鎖),同時執行緒A就進入到了該物件的等待池中。如果另外的一個執行緒呼叫了相同物件的notifyAll()方法,那麼處於該物件的等待池中的執行緒就會全部進入該物件的鎖池中,準備爭奪鎖的擁有權。如果另外的一個執行緒呼叫了相同物件的notify()方法,那麼僅僅有一個處於該物件的等待池中的執行緒(隨機)會進入該物件的鎖池.

深入理解:

如果執行緒呼叫了物件的 wait()方法,那麼執行緒便會處於該物件的等待池中,等待池中的執行緒不會去競爭該物件的鎖。

當有執行緒呼叫了物件的 notifyAll()方法(喚醒所有 wait 執行緒)或 notify()方法(只隨機喚醒一個 wait 執行緒),被喚醒的的執行緒便會進入該物件的鎖池中,鎖池中的執行緒會去競爭該物件鎖。

優先順序高的執行緒競爭到物件鎖的概率大,假若某執行緒沒有競爭到該物件鎖,它還會留在鎖池中,唯有執行緒再次呼叫 wait()方法,它才會重新回到等待池中。而競爭到物件鎖的執行緒則繼續往下執行,直到執行完了 synchronized 程式碼塊,它會釋放掉該物件鎖,這時鎖池中的執行緒會繼續競爭該物件鎖。

注:wait() ,notifyAll(),notify() 三個方法都是Object類中的方法.

關於wait() ,notifyAll(),notify() 三個方法

wait()


  1. public final void wait() throws InterruptedException,IllegalMonitorStateException

該方法用來將當前執行緒置入休眠狀態,直到接到通知或被中斷為止。在呼叫 wait()之前,執行緒必須要獲得該物件的物件級別鎖,即只能在同步方法或同步塊中呼叫 wait()方法。進入 wait()方法後,當前執行緒釋放鎖。在從 wait()返回前,執行緒與其他執行緒競爭重新獲得鎖。如果呼叫 wait()時,沒有持有適當的鎖,則丟擲 IllegalMonitorStateException,它是 RuntimeException 的一個子類,因此,不需要 try-catch 結構。

notify()


  1. public final native void notify() throws IllegalMonitorStateException

該方法也要在同步方法或同步塊中呼叫,即在呼叫前,執行緒也必須要獲得該物件的物件級別鎖,的如果呼叫 notify()時沒有持有適當的鎖,也會丟擲 IllegalMonitorStateException。

該方法用來通知那些可能等待該物件的物件鎖的其他執行緒。如果有多個執行緒等待,則執行緒規劃器任意挑選出其中一個 wait()狀態的執行緒來發出通知,並使它等待獲取該物件的物件鎖(notify 後,當前執行緒不會馬上釋放該物件鎖,wait 所在的執行緒並不能馬上獲取該物件鎖,要等到程式退出 synchronized 程式碼塊後,當前執行緒才會釋放鎖,wait所在的執行緒也才可以獲取該物件鎖),但不驚動其他同樣在等待被該物件notify的執行緒們。當第一個獲得了該物件鎖的 wait 執行緒執行完畢以後,它會釋放掉該物件鎖,此時如果該物件沒有再次使用 notify 語句,則即便該物件已經空閒,其他 wait 狀態等待的執行緒由於沒有得到該物件的通知,會繼續阻塞在 wait 狀態,直到這個物件發出一個 notify 或 notifyAll。這裡需要注意:它們等待的是被 notify 或 notifyAll,而不是鎖。這與下面的 notifyAll()方法執行後的情況不同。

notifyAll()


  1. public final native void notifyAll() throws IllegalMonitorStateException

該方法與 notify ()方法的工作方式相同,重要的一點差異是:

notifyAll 使所有原來在該物件上 wait 的執行緒統統退出 wait 的狀態(即全部被喚醒,不再等待 notify 或 notifyAll,但由於此時還沒有獲取到該物件鎖,因此還不能繼續往下執行),變成等待獲取該物件上的鎖,一旦該物件鎖被釋放(notifyAll 執行緒退出呼叫了 notifyAll 的 synchronized 程式碼塊的時候),他們就會去競爭。如果其中一個執行緒獲得了該物件鎖,它就會繼續往下執行,在它退出 synchronized 程式碼塊,釋放鎖後,其他的已經被喚醒的執行緒將會繼續競爭獲取該鎖,一直進行下去,直到所有被喚醒的執行緒都執行完畢。

sleep()不會釋放掉鎖(監控)

最開始的那道題答案是D

原文釋出時間為: 2018-11-16
本文作者: Java技術驛站
本文來自雲棲社群合作伙伴“ Java技術驛站”,瞭解相關資訊可以關注“ Java技術驛站”。


相關文章