工做三年,小胖連 wait/notify/notifyAll 都不會用?真的菜!

前幾篇複習了下線程的建立方式、線程的狀態、Thread 的源碼這幾篇文章,這篇講講 Object 幾個跟線程獲取釋放鎖相關的方法:wait、notify、notifyAll。java

wait 方法源碼解析

因爲 wait() 是 Object 類的 native 方法,在 idea 中,它長這樣:安全

public final native void wait(long timeout) throws InterruptedException;

看不了源碼,那隻能看源碼的註釋,註釋太長,我摘取一些關鍵的點出來:app

一、
Causes the current thread to wait until either another thread 
invokes the notify() method or the notifyAll() method for this object, 
or a specified amount of time has elapsed.

The current thread must own this object's monitor.

二、
In other words,waits should always occur in loops.like this one:
synchronized(obj) {
    while (condition does not hold)
        obj.wait(timeout);
    // Perform action appropriate to condition
}

三、
@throws IllegalArgumentException
if the value of timeout isnegative.

@throws IllegalMonitorStateException
if the current thread is notthe owner of the object 's monitor.

@throws InterruptedException
if any thread interrupted the current thread before or while the current thread was waiting
for a notification.
The interrupted status of the current thread is cleared when this exception is thrown.

註釋中提到幾點:ide

  • wait 會讓當前線程進入等待狀態,除非其餘線程調用了 notify 或者 notifyAll 方法喚醒它,又或者等待時間到。另外,當前線程必須持有對象監控器(也就是使用 synchronized 加鎖)oop

  • 必須把 wait 方法寫在 synchronized 保護的 while 代碼塊中,並始終判斷執行條件是否知足,若是知足就往下繼續執行,若是不知足就執行 wait 方法,而在執行 wait 方法以前,必須先持有對象的 monitor 鎖,也就是一般所說的 synchronized 加鎖。this

  • 超時時間非法,拋 IllegalArgumentException 異常;不持有對象的 monitor 鎖,拋 IllegalMonitorStateException 異常;在等待期間被其餘線程中斷,拋出 InterruptedException 異常。idea

爲何 wait 必須在 synchronized 保護的同步代碼中使用?

逆向思考下,沒有 synchronized 保護的狀況下,咱們使用會出現啥問題?先試着來模擬一個簡單的生產者消費者例子:線程

public class BlockingQueue {

    Queue<String> buffer = new LinkedList<String>();

    // 生產者,負責往隊列放數據
    public void give(String data) {
        buffer.add(data);
        notify();
    }

    // 消費者,主要是取數據
    public String take() throws InterruptedException {
        while (buffer.isEmpty()) {
            wait();
        }
        return buffer.remove();
    }

}

首先 give () 往隊列裏放數據,放完之後執行 notify 方法來喚醒以前等待的線程;take () 檢查整個 buffer 是否爲空,若是爲空就進入等待,若是不爲空就取出一個數據。但在這裏咱們並無用 synchronized 修飾。假設咱們如今只有一個生產者和一個消費者,那就有可能出現如下狀況:code

  • 此時,生產者無數據。消費者線程調用 take(),while 條件爲 true。正常來講,這時應該去調用 wait() 等待,但此時消費者在調用 wait 以前,被被調度器暫停了,還沒來得及調用 wait。orm

  • 到生產者調用 give 方法,放入數據並視圖喚醒消費者線程。可這個時候喚醒不起做用呀。消費者並無在等待。

  • 最後,消費者回去調用 wait 方法,就進入了無限等待中。

看明白了嗎?第一步時,消費者判斷了 while 條件,但真正執行 wait 方法時,生產者已放入數據,以前的 buffer.isEmpty 的結果已通過期了,由於這裏的 「判斷 - 執行」 不是一個原子操做,它在中間被打斷了,是線程不安全的

正確的寫法應該是這樣子的:如下寫法就確保永遠 notify 方法不會在 buffer.isEmpty 和 wait 方法之間被調用,也就不會有線程安全問題。

public class BlockingQueue {

    Queue<String> buffer = new LinkedList<String>();

    // 生產者,負責往隊列放數據
    public void give(String data) {
        synchronized(this) {
            buffer.add(data);
            notify();
        }
    }

    // 消費者,主要是取數據
    public String take() throws InterruptedException {
        synchronized(this) {
            while (buffer.isEmpty()) {
                wait();
            }
            return buffer.remove();
        }
    }

}

notify & notifyAll

notify & notifyAll 都是 Object 的 native 方法,在 IDEA 中看不到它的源碼,一樣是隻能看註釋。

public final native void notify();

public final native void notifyAll();

註釋中主要提到如下幾點:

  • notify() 隨機喚醒一個等待該對象鎖的線程,即便是多個也隨機喚醒其中一個(跟線程優先級無關)。
  • notifyAll() 通知全部在等待該競爭資源的線程,誰搶到鎖誰擁有執行權(跟線程優先級無關)。
  • 當前線程不持有對象的 monitor 鎖,拋 IllegalMonitorStateException 異常。

爲啥 wait & notify & notifyAll 定義在 Object 中,而 sleep 定義在 Thread 中?

兩點緣由:

  • Java 的每一個對象都有一把稱之爲 monitor 監視器的鎖,每一個對象均可以上鎖,因此在對象頭中有一個用來保存鎖信息的位置。這個鎖是對象級別的,而不是線程級別的,每一個對象都有鎖,經過線程得到。若是線程須要等待某些鎖那麼調用對象中的 wait 方法就有意義了,它等待的就是這個對象的鎖。若是 wait 方法定義在 Thread 類中,線程正在等待的是哪一個鎖就不明顯了。簡單來講,因爲 wait & notify & notifyAll 是鎖級別的操做,因此把他們定義在 Object 類中由於鎖屬於對象。

  • 再者,若是把它們定義在 Thread 中,會帶來不少問題。一個線程能夠有多把鎖,你調用 wait 或者 notify,我怎麼知道你要等待的是哪把鎖?喚醒的哪一個線程呢?

wait 和 sleep 的異同

上次的文章咱們已經看過了 sleep 的源碼了,它們的相同點主要有:

  • 它們均可以改變線程狀態,讓其進入計時等待。
  • 它們均可以響應 interrupt 中斷,並拋出 InterruptedException 異常。

不一樣點:

  • wait 是 Object 類的方法,而 sleep 是 Thread 類的方法。
  • wait 方法必須在 synchronized 保護的代碼中使用,而 sleep 方法可在任意地方。
  • 調用 sleep 方法不釋放 monitor 鎖,調用 wait 方法,會釋放 monitor 鎖。
  • sleep 時間一到立刻恢復執行(由於沒有釋放鎖);wait 須要等中斷,或者對應對象的 notify 或 notifyAll 纔會恢復,搶到鎖纔會執行(喚醒多個的狀況)。
相關文章
相關標籤/搜索