Java中wait、notify、notifyAll使用詳解

基礎知識

首先我們需要知道,這幾個都是Object對象的方法。換言之,Java中所有的對象都有這些方法。

public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public final void wait() throws InterruptedException {
    wait(0);
}

其中notify()、notifyAll()、wait(long timeout)是本地方法
其次我們需要知道這幾個方法主要是用來個線程之間通信的。那可能就有人會問,既然是用來線程之間通信的,那爲什麼這幾個方法不是在線程類Thread上呢?對於這個問題,我們先來看下這幾個方法的具體使用方式再來回答這個問題。

用法

Java中規定,在調用者三個方法時,當前線程必須獲得對象鎖。因此就得配合synchronized關鍵字來使用

//使用模式,不代表可運行代碼
synchronized(object) {
    while(contidion) {
        object.wait();
    }
    //object.notify();
    //object.notifyAll();
}

或者

public synchronized void methodName() {
    while(contidion) {
        object.wait();
    }
    //object.notify();
    //object.notifyAll();
}

在synchronized拿到對象鎖之後,synchronized代碼塊或者方法中,必定是會持有對象鎖的,因此就可以使用wait()或者notify()。
通過上述使用方法,我們也能很好理解爲什麼這幾個方法是在Object上而不是在Thread上。因爲每個對象都可以作爲synchronized鎖的對象,因此wait、notify等必須和對象關聯才能配合synchronized使用。

作用

方法 作用
wait 線程自動釋放佔有的對象鎖,並等待notify。
notify 隨機喚醒一個正在wait當前對象的線程,並讓被喚醒的線程拿到對象鎖
notifyAll 喚醒所有正在wait當前對象的線程,但是被喚醒的線程會再次去競爭對象鎖。因爲一次只有一個線程能拿到鎖,所有其他沒有拿到鎖的線程會被阻塞。推薦使用。

實際案例

接下來我們就使用wait()、notify()來實現一個生產者、消費者模式。這個也是面試過程中可能會被問到的地方。至於什麼是生產者消費者模式,不明白的同學請自行百度。
首先是一些基礎的代碼

private static Boolean run = true;//控制是否生產和消費
private static final Integer MAX_CAPACITY = 5;//緩衝區最大數量
private static final LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();//緩衝隊列

生產者代碼

/**
 * 生產者
 */
class Producter extends Thread {
    @Override
    public void run() {
        while (run) {
            synchronized (queue) {
                while (queue.size() >= MAX_CAPACITY * 2) {
                    try {
                        System.out.println("緩衝隊列已滿,等待消費");
                        queue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                try {
                    String string = UUID.randomUUID().toString();
                    queue.put(string);
                    System.out.println("生產:" + string);
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                queue.notifyAll();//通知生產者和消費者
            }
        }
    }
}

消費者代碼

/**
 * 消費者
 */
class Consumer extends Thread {
    @Override
    public void run() {
        while (run) {
            synchronized (queue) {
                while (queue.isEmpty()) {
                    try {
                        System.out.println("隊列爲空,等待生產");
                        queue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                try {
                    System.out.println("消費:" + queue.take());
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                queue.notifyAll();//通知生產者和消費者
            }
        }
    }
}

代碼說明

1、生產者和消費者都繼承了線程Thread,因爲wait、notify本身就是線程間通信使用
2、生產者和消費者都有兩層while,外層的while是用來判斷是否運行生產者和消費者。內存的while用來判斷隊列queue是否已滿或者爲空,如果滿足條件,則使得當前線程變成等待狀態(等待notify)。
3、內層的條件判斷爲什麼用while不用if,原因是當線程被wait之後,會釋放對象鎖。當等待的線程被notify之後,必須再次嘗試去獲取對象鎖,如果沒有獲取到對象鎖,那還必須等待,直到拿到對象鎖之後才能向後執行。
4、當生產者生產了一個數據或者消費者消費了一個數據之後,使用notifyAll()方法來通知所有等待當前對象鎖的線程,但是一次只會有一個等待的線程能拿到鎖。
5、我們使用queue作爲鎖的對象在不同線程之間進行通信

代碼運行結果

隊列爲空,等待生產
生產:e422484e-8eb3-4a91-8a7c-97e21d7ef498
生產:7894b802-2529-4798-ba98-9f0657f39240
生產:848f6759-a427-4a94-89dc-3f484daaa467
生產:f711d3dc-972c-4c44-8640-faffe376d354
生產:38a08e62-d774-4ed5-8b51-f1ad534c246f
消費:e422484e-8eb3-4a91-8a7c-97e21d7ef498
消費:7894b802-2529-4798-ba98-9f0657f39240
消費:848f6759-a427-4a94-89dc-3f484daaa467
消費:f711d3dc-972c-4c44-8640-faffe376d354
消費:38a08e62-d774-4ed5-8b51-f1ad534c246f
隊列爲空,等待生產
生產:9fae26f3-0b6e-4fbd-9620-040667efe0af
生產:95bb1d88-e08a-4f70-a270-f75760994184
消費:9fae26f3-0b6e-4fbd-9620-040667efe0af
消費:95bb1d88-e08a-4f70-a270-f75760994184
隊列爲空,等待生產
生產:13d304bc-dff3-44a4-9527-2e0facd884e7
生產:2693e069-bae1-4beb-adcd-a3c3bf5d232b
消費:13d304bc-dff3-44a4-9527-2e0facd884e7
消費:2693e069-bae1-4beb-adcd-a3c3bf5d232b

由結果也可以看出來,生產和消費是無規則的,因爲雖然notifyAll()通知了所有的等待線程,但是不確定那個線程中能拿到對象鎖。但是也有一個很明顯的問題,因爲同時只有一個線程能拿到對象鎖,生產者和消費者不可能同時運行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章