溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Java線程通信中wait-notify通信的方式是什么

發(fā)布時間:2022-03-02 09:18:33 來源:億速云 閱讀:146 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要介紹“Java線程通信中wait-notify通信的方式是什么”的相關(guān)知識,小編通過實(shí)際案例向大家展示操作過程,操作方法簡單快捷,實(shí)用性強(qiáng),希望這篇“Java線程通信中wait-notify通信的方式是什么”文章能幫助大家解決問題。

    1. 線程通信的定義

    線程是操作系統(tǒng)調(diào)度的最小單位,有自己的??臻g,可以按照既定的代碼逐步執(zhí)行,但是如果每個線程間都孤立地運(yùn)行,就會造資源浪費(fèi)。所以在現(xiàn)實(shí)中,如果需要多個線程按照指定的規(guī)則共同完成一個任務(wù),那么這些線程之間就需要互相協(xié)調(diào),這個過程被稱為線程的通信。

    線程的通信可以被定義為:當(dāng)多個線程共同操作共享的資源時,線程間通過某種方式互相告知自己的狀態(tài),以避免無效的資源爭奪。

    線程間通信的方式可以有很多種:等待-通知、共享內(nèi)存、管道流。“等待-通知”通信方式是Java中使用普遍的線程間通信方式,其經(jīng)典的案例是“生產(chǎn)者-消費(fèi)者”模式。

    2. 為什么需要wait-notify?

    場景:有幾個小孩都想進(jìn)入房間內(nèi)使用算盤(CPU)進(jìn)行計算,老王(操作系統(tǒng))就使用了一把鎖(synchronized)讓同一時間只有一個小孩能進(jìn)入房間使用算盤,于是他們排隊進(jìn)入房間。

    (1) 小南最先獲取到了鎖,進(jìn)入到房間內(nèi),但是由于條件不滿足(沒煙干不了活),小南不能繼續(xù)進(jìn)行計算 ,但小南如果一直占用著鎖,其它人就得一直阻塞,效率太低。

    Java線程通信中wait-notify通信的方式是什么

    (2) 于是老王單開了一間休息室(調(diào)用 wait 方法),讓小南到休息室(WaitSet)等著去了,這時鎖釋放開, 其它人可以由老王隨機(jī)安排進(jìn)屋

    (3) 直到小M將煙送來,大叫一聲 [ 你的煙到了 ] (調(diào)用 notify 方法)

    Java線程通信中wait-notify通信的方式是什么

    (4) 小南于是可以離開休息室,重新進(jìn)入競爭鎖的隊列

    Java線程通信中wait-notify通信的方式是什么

    java語言中“等待-通知”方式的線程間通信使用對象的wait()、notify()兩類方法來實(shí)現(xiàn)。每個java對象都有wait()、notify()兩類實(shí)例方法,并且wait()、notify()方法和對象的監(jiān)視器是緊密相關(guān)的。

    wait()、notify()兩類方法在數(shù)量上不止兩個。wait()、notify()兩類方法不屬于Thread類,而是屬于java對象實(shí)例。

    3. wait方法和notify方法

    java對象中的wait()、notify()兩類方法就如同信號開關(guān),用于等待方和通知方之間的交互。

    1、對象的wait()方法

    對象的wait()方法的主要作用是讓當(dāng)前線程阻塞并等待被喚醒。wait()方法與對象監(jiān)視器緊密相關(guān),使用wait()方法時一定要放在同步塊中。wait()方法的調(diào)用方法如下:

    public class Main {
        static final Object lock = new Object();
        public static void method1() throws InterruptedException {
            synchronized( lock ) {
                lock.wait();
            }
        }
    }

    Object類中的wait()方法有三個版本:

    (1) void wait():當(dāng)前線程調(diào)用了同步對象lockwait()實(shí)例方法后,將導(dǎo)致當(dāng)前的線程等待,當(dāng)前線程進(jìn)入lock的監(jiān)視器WaitSet,等待被其他線程喚醒;

    (2) void wait(long timeout):限時等待。導(dǎo)致當(dāng)前的線程等待,等待被其他線程喚醒,或者指定的時間timeout用完,線程不再等待;

    (3) void wait(long timeout,int nanos):高精度限時等待,其主要作用是更精確地控制等待時間。參數(shù)nanos是一個附加的納秒級別的等待時間;

    2、對象的notify()方法

    對象的notify()方法的主要作用是喚醒在等待的線程。notify()方法與對象監(jiān)視器緊密相關(guān),調(diào)用notify()方法時也需要放在同步塊中。notify()方法的調(diào)用方法如下:

    public class Main {
        static final Object lock = new Object();
        public static void method1() throws InterruptedException {
            synchronized( lock ) {
                lock.notify();
            }
        }
    }

    notify()方法有兩個版本:

    (1)void notify()lock.notify()調(diào)用后,喚醒lock監(jiān)視器等待集中的第一條等待線程;被喚醒的線程進(jìn)入EntryList,其狀態(tài)從WAITING變成BLOCKED。

    (2) void notifyAll()lock.notifyAll()被調(diào)用后,喚醒lock監(jiān)視器等待集中的全部等待線程,所有被喚醒的線程進(jìn)入EntryList,線程狀態(tài)從WAITING變成BLOCKED。

    小結(jié):

    obj.wait():讓進(jìn)入Object監(jiān)視器的線程到waitset等待

    obj.notify():在Object上正在waitset等待的線程中挑一個喚醒

    obj.notifyAll():讓在Object上正在waitset等待的線程全部喚醒

    4. wait方法和notify方法的原理

    對象的wait()方法的核心原理大致如下:

    (1) 當(dāng)線程調(diào)用了lock(某個同步鎖對象)的wait()方法后,jvm會將當(dāng)前線程加入lock監(jiān)視器的WaitSet(等待集),等待被其他線程喚醒。

    (2) 當(dāng)前線程會釋放lock對象監(jiān)視器的Owner權(quán)利,讓其他線程可以搶奪lock對象的監(jiān)視器。

    (3) 讓當(dāng)前線程等待,其狀態(tài)變成WAITING。在線程調(diào)用了同步對象lock的wait()方法之后,同步對象lock的監(jiān)視器內(nèi)部狀態(tài)大致如圖2-15所示。

    對象的notify()或者notifyAll()方法的原理大致如下:

    (1) 當(dāng)線程調(diào)用了lock(某個同步鎖對象)的notify()方法后,jvm會喚醒lock監(jiān)視器WaitSet中的第一條等待線程。

    (2) 當(dāng)線程調(diào)用了locknotifyAll()方法后,jvm會喚醒lock監(jiān)視器WaitSet中的所有等待線程。

    (3) 等待線程被喚醒后,會從監(jiān)視器的WaitSet移動到EntryList,線程具備了排隊搶奪監(jiān)視器Owner權(quán)利的資格,其狀態(tài)從WAITING變成BLOCKED。

    (4) EntryList中的線程搶奪到監(jiān)視器的Owner權(quán)利之后,線程的狀態(tài)從BLOCKED變成Runnable,具備重新執(zhí)行的資格。

    Java線程通信中wait-notify通信的方式是什么

    (1) Owner 線程發(fā)現(xiàn)條件不滿足,調(diào)用wait 方法,即可進(jìn)入WaitSet,變?yōu)?WAITING 狀態(tài) ;

    (2) BLOCKED 和WAITING 的線程都處于阻塞狀態(tài),不占用CPU時間片 ;

    (3) BLOCKED:線程會在Owner 線程釋放鎖時喚醒 ;

    (4) WAITING :線程會在Owner 線程調(diào)用notify 或 notifyAll時喚醒,但喚醒后并不意味者立刻獲得鎖,仍需進(jìn)入 EntryList 重新競爭;

    5. wait方法和notify方法示例

    1、進(jìn)入Object監(jiān)視器的線程才能調(diào)用wait()方法

    小南并不能直接進(jìn)入WaitSet休息室,而是獲取鎖進(jìn)入房間后才能進(jìn)入休息室,沒有鎖的話小南沒法進(jìn)入房間,更沒法進(jìn)入休息室。他進(jìn)入休息室后就會釋放鎖,讓其他線程競爭鎖進(jìn)入房間。

    Java線程通信中wait-notify通信的方式是什么

    public class Main {
        static final Object lock = new Object();
        public static void main(String[] args) {
            synchronized (lock){
                try {
                    // 只有成為了monitor對象的owner,即獲得了對象鎖之后,才有資格進(jìn)入waitset等待
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    2、進(jìn)入Object監(jiān)視器的線程才能調(diào)用notify()方法

    小M此時獲取到了鎖,進(jìn)入了房間,并喚醒了在休息室中等待的小王,小M如果獲取到鎖進(jìn)行房間時沒有辦法喚醒在休息室等待的小王的,因?yàn)榇藭r小M在門外,小王根本聽不到。

    Java線程通信中wait-notify通信的方式是什么

    使用notify()喚醒等待區(qū)的一個線程:

    public class Main {
        static final Object lock = new Object();
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(()->{
                System.out.println("t1線程開始執(zhí)行...");
                synchronized (lock){
                    try {
                        // 讓t1線程在lock鎖的waitset中等待
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 被喚醒后,繼續(xù)執(zhí)行
                    System.out.println("線程t1被喚醒...");
                }
            },"t1");
            t1.start();
            Thread t2 = new Thread(()->{
                System.out.println("t2線程開始執(zhí)行...");
                synchronized (lock){
                    try {
                        // 讓t2線程在lock鎖的waitset中等待
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 被喚醒后,繼續(xù)執(zhí)行
                System.out.println("線程t2被喚醒...");
            },"t2");
            t2.start();
            Thread.sleep(2000);
            System.out.println("喚醒lock鎖上等待的線程...");
            synchronized (lock){
                // 主線程拿到鎖后,喚醒正在休息室中等待的某一個線程
                lock.notify();
            }
        }
    }

    執(zhí)行結(jié)果:

    t1線程開始執(zhí)行...
    t2線程開始執(zhí)行...
    喚醒lock鎖上等待的線程...
    線程t1被喚醒...

    使用notifyAll()喚醒等待區(qū)所有的線程:

    public class Main {
        static final Object lock = new Object();
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(()->{
                System.out.println("t1線程開始執(zhí)行...");
                synchronized (lock){
                    try {
                        // 讓t1線程在lock鎖的waitset中等待
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 被喚醒后,繼續(xù)執(zhí)行
                    System.out.println("線程t1被喚醒...");
                }
            },"t1");
    
            t1.start();
            Thread t2 = new Thread(()->{
                System.out.println("t2線程開始執(zhí)行...");
                synchronized (lock){
                    try {
                        // 讓t2線程在lock鎖的waitset中等待
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 被喚醒后,繼續(xù)執(zhí)行
                System.out.println("線程t2被喚醒...");
            },"t2");
            t2.start();
    
            Thread.sleep(2000);
    
            System.out.println("喚醒lock鎖上等待的線程...");
            synchronized (lock){
                // 主線程拿到鎖后,喚醒正在休息室中等待的所有線程
                lock.notifyAll();
            }
        }
    }

    執(zhí)行結(jié)果:

    t1線程開始執(zhí)行...
    t2線程開始執(zhí)行...
    喚醒lock鎖上等待的線程...
    線程t2被喚醒...
    線程t1被喚醒...

    6. 為什么 wait 和 notify 方法要在同步塊中調(diào)用?

    在調(diào)用同步對象的wait()和notify()系列方法時,“當(dāng)前線程”必須擁有該對象的同步鎖,也就是說,wait()和notify()系列方法需要在同步塊中使用,否則JVM會拋出類似如下的異常:

    Java線程通信中wait-notify通信的方式是什么

    為什么wait和notify不在synchronized同步塊的內(nèi)部使用會拋出異常呢?這需要從wait()和notify()方法的原理說起。

    wait()方法的原理:

    首先,JVM會釋放當(dāng)前線程的對象鎖監(jiān)視器的Owner資格;其次,JVM會將當(dāng)前線程移入監(jiān)視器的WaitSet隊列,而這些操作都和對象鎖監(jiān)視器是相關(guān)的。所以,wait()方法必須在synchronized同步塊的內(nèi)部調(diào)用。在當(dāng)前線程執(zhí)行wait()方法前,必須通過synchronized()方法成為對象鎖的監(jiān)視器的Owner。

    notify()方法的原理:

    JVM從對象鎖的監(jiān)視器的WaitSet隊列移動一個線程到其EntryList隊列,這些操作都與對象鎖的監(jiān)視器有關(guān)。所以,notify()方法也必須在synchronized同步塊的內(nèi)部調(diào)用。在執(zhí)行notify()方法前,當(dāng)前線程也必須通過synchronized()方法成為對象鎖的監(jiān)視器的Owner。

    關(guān)于“Java線程通信中wait-notify通信的方式是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識,可以關(guān)注億速云行業(yè)資訊頻道,小編每天都會為大家更新不同的知識點(diǎn)。

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI