wait/notify/notifyAll 總結(jié)

概述

在Java中,可以通過配合調(diào)用Object對象的wait()方法和notify()方法或notifyAll()方法來實現(xiàn)線程間的通信。在線程中調(diào)用wait()方法,將阻塞,然后等待其他線程通過調(diào)用notify()方法或notifyAll()發(fā)起的通知,在線程中調(diào)用notify()方法或notifyAll()方法,將通知其他線程從wait()方法處返回。

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

wait()

public final void wait()  
throws InterruptedException,IllegalMonitorStateException

該方法用來將當前線程置入休眠狀態(tài),直到接到通知或被中斷為止。在調(diào)用wait()之前,線程必須要獲得該對象的對象級別鎖,即只能在同步方法或同步塊中調(diào)用wait()方法。進入wait()方法后,當前線程釋放鎖。在從wait()返回前,線程與其他線程競爭重新獲得鎖。如果調(diào)用wait()時,沒有持有適當?shù)逆i,則拋出IllegalMonitorStateException,它是RuntimeException的一個子類,因此,不需要try-catch結(jié)構(gòu)。

notify()

public final native void notify()
throws IllegalMonitorStateException

該方法也要在同步方法或同步塊中調(diào)用,即在調(diào)用前,線程也必須要獲得該對象的對象級別鎖,的如果調(diào)用notify()時沒有持有適當?shù)逆i,也會拋出IllegalMonitorStateException。

該方法用來通知那些可能等待該對象的對象鎖的其他線程。如果有多個線程等待,則線程規(guī)劃器任意挑選出其中一個wait()狀態(tài)的線程來發(fā)出通知,并使它等待獲取該對象的對象鎖(notify后,當前線程不會馬上釋放該對象鎖,wait所在的線程并不能馬上獲取該對象鎖,要等到程序退出synchronized代碼塊后,當前線程才會釋放鎖,wait所在的線程也才可以獲取該對象鎖),但不驚動其他同樣在等待被該對象notify的線程們。當?shù)谝粋€獲得了該對象鎖的wait線程運行完畢以后,它會釋放掉該對象鎖,此時如果該對象沒有再次使用notify語句,則即便該對象已經(jīng)空閑,其他wait狀態(tài)等待的線程由于沒有得到該對象的通知,會繼續(xù)阻塞在wait狀態(tài),直到這個對象發(fā)出一個notify或notifyAll。這里需要注意:它們等待的是被notify或notifyAll,而不是鎖。這與下面的notifyAll()方法執(zhí)行后的情況不同。

notifyAll()

public final native void notifyAll() 
throws IllegalMonitorStateException

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

notifyAll使所有原來在該對象上wait的線程統(tǒng)統(tǒng)退出wait的狀態(tài)(即全部被喚醒,不再等待notify或notifyAll,但由于此時還沒有獲取到該對象鎖,因此還不能繼續(xù)往下執(zhí)行),變成等待獲取該對象上的鎖,一旦該對象鎖被釋放(notifyAll線程退出調(diào)用了notifyAll的synchronized代碼塊的時候),他們就會去競爭。如果其中一個線程獲得了該對象鎖,它就會繼續(xù)往下執(zhí)行,在它退出synchronized代碼塊,釋放鎖后,其他的已經(jīng)被喚醒的線程將會繼續(xù)競爭獲取該鎖,一直進行下去,直到所有被喚醒的線程都執(zhí)行完畢(與 notify 不同的點)。

wait(long)和wait(long,int)

顯然,這兩個方法是設(shè)置等待超時時間的,后者在超值時間上加上ns,精度也難以達到,因此,該方法很少使用。對于前者,如果在等待線程接到通知或被中斷之前,已經(jīng)超過了指定的毫秒數(shù),則它通過競爭重新獲得鎖,并從wait(long)返回。另外,需要知道,如果設(shè)置了超時時間,當wait()返回時,我們不能確定它是因為接到了通知還是因為超時而返回的,因為wait()方法不會返回任何相關(guān)的信息。但一般可以通過設(shè)置標志位來判斷,在notify之前改變標志位的值,在wait()方法后讀取該標志位的值來判斷,當然為了保證notify不被遺漏,我們還需要另外一個標志位來循環(huán)判斷是否調(diào)用wait()方法。

消費者、生產(chǎn)者

生產(chǎn)者-消費者(producer-consumer)問題,也稱作有界緩沖區(qū)(bounded-buffer)問題,兩個進程共享一個公共的固定大小的緩沖區(qū)。其中一個是生產(chǎn)者,用于將消息放入緩沖區(qū);另外一個是消費者,用于從緩沖區(qū)中取出消息。問題出現(xiàn)在當緩沖區(qū)已經(jīng)滿了,而此時生產(chǎn)者還想向其中放入一個新的數(shù)據(jù)項的情形,其解決方法是讓生產(chǎn)者此時進行休眠,等待消費者從緩沖區(qū)中取走了一個或者多個數(shù)據(jù)后再去喚醒它。同樣地,當緩沖區(qū)已經(jīng)空了,而消費者還想去取消息,此時也可以讓消費者進行休眠,等待生產(chǎn)者放入一個或者多個數(shù)據(jù)時再喚醒它。

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

總結(jié)

  • 如果線程調(diào)用了對象的wait()方法,那么線程便會處于該對象的等待池中,等待池中的線程不會去競爭該對象的鎖。
  • 當有線程調(diào)用了對象的notifyAll()方法(喚醒所有wait線程)或notify()方法(只隨機喚醒一個wait線程),被喚醒的的線程便會進入該對象的鎖池中,鎖池中的線程會去競爭該對象鎖。
  • 優(yōu)先級高的線程競爭到對象鎖的概率大,假若某線程沒有競爭到該對象鎖,它還會留在鎖池中,唯有線程再次調(diào)用wait()方法,它才會重新回到等待池中。而競爭到對象鎖的線程則繼續(xù)往下執(zhí)行,直到執(zhí)行完了synchronized代碼塊,它會釋放掉該對象鎖,這時鎖池中的線程會繼續(xù)競爭該對象鎖。

參考

wait/notify/notifyAll實現(xiàn)線程間通信
生產(chǎn)者-消費者模型的Java實現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容