天天看點

(Java并發基礎)Object的wait/notify/notifyAll與Thread的關系為什麼wait方法必須在synchronized保護的同步代碼中使用?為什麼wait/notify/notifyAll被定義在Object類中,而Sleep定義在Thread類中?wait/notify和sleep方法的異同?

首先我們要知道Java中的每個類的父類都是Object,而Object中對象頭中有個位置用來儲存鎖資訊。這塊将在進階部分進行深入講解。

為什麼wait方法必須在synchronized保護的同步代碼中使用?

此方法會導緻目前線程将自己放入該對象的等待集中,然後放棄對此對象的所有同步聲明。線程T出于線程排程目的而被禁用,并處于休眠狀态。其實就是讓目前線程運作該該對象處,并讓出CPU。

先來段源碼,因為源碼說要“該方法的調用必須是擁有對象的鎖"。也就是通過synchronized方法或代碼塊擷取對象的鎖。但是為啥要這麼設計呢?其實要從該方法的作用說起,該方法是讓CPU讓出,停在該對象處的等待隊列中。如果不加鎖,其他線程就可以随意進入該對象并修改該對象的狀态。

* <p>
     * A thread can also wake up without being notified, interrupted, or
     * timing out, a so-called <i>spurious wakeup</i>.  While this will rarely
     * occur in practice, applications must guard against it by testing for
     * the condition that should have caused the thread to be awakened, and
     * continuing to wait if the condition is not satisfied.  In other words,
     * waits should always occur in loops, like this one:
     * <pre>
     *     synchronized (obj) {
     *         while (&lt;condition does not hold&gt;)
     *             obj.wait(timeout);
     *         ... // Perform action appropriate to condition
     *     }
     * </pre>
     * * This method should only be called by a thread that is the owner
     * of this object's monitor. See the {@code notify} method for a
     * description of the ways in which a thread can become the owner of
     * a monitor. 
           

這裡還存在一個“虛假喚醒”(spurious wakeup)的問題,線程可能在既沒有被notify/notifyAll,也沒有被中斷或者逾時的情況下被喚醒,這種喚醒是我們不希望看到的。雖然在實際生産中,虛假喚醒發生的機率很小,但是程式依然需要保證在發生虛假喚醒的時候的正确性,是以就需要采用while循環的結構。

while (condition does not hold)
    obj.wait();
           

這樣即使被虛假喚醒,也會再次檢查while裡邊的條件,如果不滿足條件,就會繼續wait,也就消除了虛假喚醒的風險。

為什麼wait/notify/notifyAll被定義在Object類中,而Sleep定義在Thread類中?

主要有兩點原因:

  1. 因為 Java 中每個對象都有一把稱之為 monitor 螢幕的鎖,由于每個對象都可以上鎖,這就要求在對象頭中有一個用來儲存鎖資訊的位置。這個鎖是對象級别的,而非線程級别的,wait/notify/notifyAll 也都是鎖級别的操作,它們的鎖屬于對象,是以把它們定義在 Object 類中是最合适,因為 Object 類是所有對象的父類。

    因為如果把 wait/notify/notifyAll 方法定義在 Thread 類中,會帶來很大的局限性,比如一個線程可能持有多把鎖,以便實作互相配合的複雜邏輯,假設此時 wait 方法定義在 Thread 類中,如何實作讓一個線程持有多把鎖呢?又如何明确線程等待的是哪把鎖呢?既然我們是讓目前線程去等待某個對象的鎖,自然應該通過操作對象來實作,而不是操作線程。

wait/notify和sleep方法的異同?

主要對比 wait 和 sleep 方法,我們先說相同點:

  1. 它們都可以讓線程阻塞。
  2. 它們都可以響應 interrupt 中斷:在等待的過程中如果收到中斷信号,都可以進行響應,并抛出 InterruptedException 異常。

    不同點:

  3. wait 方法必須在 synchronized 保護的代碼中使用,而 sleep 方法并沒有這個要求。
  4. 在同步代碼中執行 sleep 方法時,并不會釋放 monitor 鎖,但執行 wait 方法時會主動釋放 monitor 鎖。
  5. sleep 方法中會要求必須定義一個時間,時間到期後會主動恢複,而對于沒有參數的 wait 方法而言,意味着永久等待,直到被中斷或被喚醒才能恢複,它并不會主動恢複。
  6. wait/notify 是 Object 類的方法,而 sleep 是 Thread 類的方法。

    以上就是關于 wait/notify 與 sleep 的異同點。