wait/notify/notifyAll 總結

weixin_34007291發表於2017-06-28

概述

在Java中,可以通過配合呼叫Object物件的wait()方法和notify()方法或notifyAll()方法來實現執行緒間的通訊。線上程中呼叫wait()方法,將阻塞,然後等待其他執行緒通過呼叫notify()方法或notifyAll()發起的通知,線上程中呼叫notify()方法或notifyAll()方法,將通知其他執行緒從wait()方法處返回。

Object是所有類的超類,它有5個方法組成了等待/通知機制的核心:notify()、notifyAll()、wait()、wait(long)和wait(long,int)。在Java中,所有的類都從Object繼承而來,因此,所有的類都擁有這些共有方法可供使用。而且,由於他們都被宣告為final,因此在子類中不能覆寫任何一個方法。

wait()

public final void wait()  
throws InterruptedException,IllegalMonitorStateException

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

notify()

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()

public final native void notifyAll() 
throws IllegalMonitorStateException

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

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

wait(long)和wait(long,int)

顯然,這兩個方法是設定等待超時時間的,後者在超值時間上加上ns,精度也難以達到,因此,該方法很少使用。對於前者,如果在等待執行緒接到通知或被中斷之前,已經超過了指定的毫秒數,則它通過競爭重新獲得鎖,並從wait(long)返回。另外,需要知道,如果設定了超時時間,當wait()返回時,我們不能確定它是因為接到了通知還是因為超時而返回的,因為wait()方法不會返回任何相關的資訊。但一般可以通過設定標誌位來判斷,在notify之前改變標誌位的值,在wait()方法後讀取該標誌位的值來判斷,當然為了保證notify不被遺漏,我們還需要另外一個標誌位來迴圈判斷是否呼叫wait()方法。

消費者、生產者

生產者-消費者(producer-consumer)問題,也稱作有界緩衝區(bounded-buffer)問題,兩個程式共享一個公共的固定大小的緩衝區。其中一個是生產者,用於將訊息放入緩衝區;另外一個是消費者,用於從緩衝區中取出訊息。問題出現在當緩衝區已經滿了,而此時生產者還想向其中放入一個新的資料項的情形,其解決方法是讓生產者此時進行休眠,等待消費者從緩衝區中取走了一個或者多個資料後再去喚醒它。同樣地,當緩衝區已經空了,而消費者還想去取訊息,此時也可以讓消費者進行休眠,等待生產者放入一個或者多個資料時再喚醒它。

public class ProducerConsumer {
    private LinkedList<Object> storeHouse = new LinkedList<Object>();
    private int MAX = 10;

    public ProducerConsumer() {
    }
    public void start() {
        new Producer().start();
        new Comsumer().start();
    }

    class Producer extends Thread {
        public void run() {
            while (true) {
                synchronized (storeHouse) {
                    try {
                        while (storeHouse.size() == MAX) {
                            System.out.println("storeHouse is full , please wait");
                            storeHouse.wait();
                        }
                        Object newOb = new Object();
                        if (storeHouse.add(newOb)) {
                            System.out.println("Producer put a Object to storeHouse");
                            Thread.sleep((long) (Math.random() * 3000));
                            storeHouse.notify();
                        }
                    } catch (InterruptedException ie) {
                        System.out.println("producer is interrupted!");
                    }

                }
            }
        }
    }

    class Comsumer extends Thread {
        public void run() {
            while (true) {
                synchronized (storeHouse) {
                    try {
                        while (storeHouse.size() == 0) {
                            System.out.println("storeHouse is empty , please wait");
                            storeHouse.wait();
                        }
                        storeHouse.removeLast();
                        System.out.println("Comsumer get  a Object from storeHouse");
                        Thread.sleep((long) (Math.random() * 3000));
                        storeHouse.notify();
                    } catch (InterruptedException ie) {
                        System.out.println("Consumer is interrupted");
                    }

                }
            }

        }
    }

    public static void main(String[] args) throws Exception {
        ProducerConsumer pc = new ProducerConsumer();
        pc.start();
    }
}

總結

  • 如果執行緒呼叫了物件的wait()方法,那麼執行緒便會處於該物件的等待池中,等待池中的執行緒不會去競爭該物件的鎖。
  • 當有執行緒呼叫了物件的notifyAll()方法(喚醒所有wait執行緒)或notify()方法(只隨機喚醒一個wait執行緒),被喚醒的的執行緒便會進入該物件的鎖池中,鎖池中的執行緒會去競爭該物件鎖。
  • 優先順序高的執行緒競爭到物件鎖的概率大,假若某執行緒沒有競爭到該物件鎖,它還會留在鎖池中,唯有執行緒再次呼叫wait()方法,它才會重新回到等待池中。而競爭到物件鎖的執行緒則繼續往下執行,直到執行完了synchronized程式碼塊,它會釋放掉該物件鎖,這時鎖池中的執行緒會繼續競爭該物件鎖。

參考

wait/notify/notifyAll實現執行緒間通訊
生產者-消費者模型的Java實現

相關文章