溫馨提示×

溫馨提示×

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

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

Java中怎么正確使用wait-notify方法

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

本篇內(nèi)容介紹了“Java中怎么正確使用wait-notify方法”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!

1. sleep(long n) 和 wait(long n) 的區(qū)別

(1) sleep 是 Thread 方法,而 wait 是 Object 的方法 ;

(2) sleep 不需要強制和 synchronized 配合使用,但 wait 需要 和 synchronized 一起用 ;

(3) sleep 在睡眠的同時,不會釋放對象鎖的,但 wait 在等待的時候會釋放對象鎖 ;

(4) 它們 狀態(tài) TIMED_WAITING;

@Slf4j
public class Test1 {
    private static final Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (lock){
                log.debug("t1線程獲得鎖....");
                try {
                    Thread.sleep(20000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t1").start();
        // 等待1s,讓線程去執(zhí)行t1線程
        Thread.sleep(1000);
        // 獲取不到鎖,因為t1線程在睡眠期間并不會釋放鎖
        synchronized (lock){
            log.debug("主線程想要獲取鎖....");
        }
    }
}

執(zhí)行sleep()方法后的結(jié)果:在線程t1睡眠期間,主線程沒有獲得鎖

10:34:34.574 [t1] DEBUG com.example.test.Test1 - t1線程獲得鎖....

@Slf4j
public class Test1 {
    private static final Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (lock){
                log.debug("t1線程獲得鎖....");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t1").start();
        // 等待1s,讓線程去執(zhí)行t1線程
        Thread.sleep(1000);
        // 獲取不到鎖,因為t1線程在睡眠期間并不會釋放鎖
        synchronized (lock){
            log.debug("主線程想要獲取鎖....");
        }
    }
}

執(zhí)行wait()方法后的結(jié)果:線程t1等待20s,在線程t1等待期間,主線程獲得了鎖

10:36:22.723 [t1] DEBUG com.example.test.Test1 - t1線程獲得鎖....
10:36:23.721 [main] DEBUG com.example.test.Test1 - 主線程想要獲取鎖....

2. 正確使用wait-notify方法 [while(條件)+wait]

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

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

Java中怎么正確使用wait-notify方法

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

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

Java中怎么正確使用wait-notify方法

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

Java中怎么正確使用wait-notify方法

下面我們看如何正確的實現(xiàn)這個場景

2.1 問題1

@Slf4j
public class Test2 {
    private static final Object room = new Object();
    // 是否有煙,默認沒有
    private static boolean hasCigarette = false;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (room){
                log.debug("有煙沒?[{}]", hasCigarette);
                if(!hasCigarette){
                    log.debug("沒煙,先歇會!");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有煙沒?[{}]", hasCigarette);
                if(hasCigarette){
                    log.debug("有煙,[{}],可以開始干活了", hasCigarette);
                }
            }
        },"小南").start();
        // 其他5個線程也想獲取鎖進入房間
        for(int i=0;i<5;i++){
            new Thread(()->{
                synchronized (room){
                    log.debug("可以開始干活了");
                }
            },"其他人").start();
        }
        // 主線程等待1s
        Thread.sleep(1000);
        // 因為小南線程使用sleep()方法,因此他在睡眠期間并不釋放鎖,送煙的沒辦法拿到鎖進入房間送煙
        new Thread(()->{
            synchronized (room){
                hasCigarette = true;
            }
        },"送煙的").start();
    }
}

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

11:10:50.556 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:10:50.565 [小南] DEBUG com.example.test.Test2 - 沒煙,先歇會!
11:10:52.565 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以開始干活了
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以開始干活了
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以開始干活了
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以開始干活了
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以開始干活了

(1) 小南線程在睡眠期間并不釋放鎖,因此其他線程線程也沒辦法獲取到鎖進入房間,送煙線程就沒辦法送煙;

(2) 其它干活的線程,都要一直阻塞,效率太低 ;

要解決上述的問題,需要使用wait-notify機制

2.2 問題2

@Slf4j
public class Test2 {
    private static final Object room = new Object();
    // 是否有煙,默認沒有
    private static boolean hasCigarette = false;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (room){
                log.debug("有煙沒?[{}]", hasCigarette);
                if(!hasCigarette){
                    log.debug("沒煙,先歇會!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有煙沒?[{}]", hasCigarette);
                if(hasCigarette){
                    log.debug("有煙,[{}],可以開始干活了", hasCigarette);
                }
            }
        },"小南").start();

        // 其他5個線程也想獲取鎖進入房間
        for(int i=0;i<5;i++){
            new Thread(()->{
                synchronized (room){
                    log.debug("可以開始干活了");
                }
            },"其他人").start();
        }

        // 主線程等待1s
        Thread.sleep(1000);

        // 送煙的,喚醒正在睡眠的小南線程
        new Thread(()->{
            synchronized (room){
                hasCigarette = true;
                room.notify();
            }
        },"送煙的").start();
    }
}

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

11:21:36.775 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:21:36.780 [小南] DEBUG com.example.test.Test2 - 沒煙,先歇會!
11:21:36.780 [其他人] DEBUG com.example.test.Test2 - 可以開始干活了
11:21:36.780 [其他人] DEBUG com.example.test.Test2 - 可以開始干活了
11:21:36.781 [其他人] DEBUG com.example.test.Test2 - 可以開始干活了
11:21:36.781 [其他人] DEBUG com.example.test.Test2 - 可以開始干活了
11:21:36.781 [其他人] DEBUG com.example.test.Test2 - 可以開始干活了
11:21:37.773 [小南] DEBUG com.example.test.Test2 - 有煙沒?[true]
11:21:37.774 [小南] DEBUG com.example.test.Test2 - 有煙,[true],可以開始干活了

解決了其他線程阻塞問題,但是如果有其他線程也在等待呢?就是說等待的線程不止小南一個,那么會不會喚醒錯了呢?

2.3 問題3

@Slf4j
public class Test2 {
    private static final Object room = new Object();
    // 是否有煙,默認沒有
    private static boolean hasCigarette = false;
    // 是否有外賣,默認沒有
    static boolean hasTakeout = false;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (room){
                log.debug("有煙沒?[{}]", hasCigarette);
                if(!hasCigarette){
                    log.debug("沒煙,先歇會!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有煙沒?[{}]", hasCigarette);
                if(hasCigarette){
                    log.debug("有煙,[{}],可以開始干活了", hasCigarette);
                }else {
                    log.debug("沒干成活..");
                }
            }
        },"小南").start();

        new Thread(()->{
            synchronized (room){
                log.debug("有外賣沒?[{}]", hasTakeout);
                if(!hasTakeout){
                    log.debug("沒外賣,先歇會!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有外賣沒?[{}]", hasTakeout);
                if(hasTakeout){
                    log.debug("有外賣,[{}],可以開始干活了", hasTakeout);
                }else{
                    log.debug("沒干成活..");
                }
            }
        },"小女").start();
        // 主線程等待1s
        Thread.sleep(1000);
        new Thread(()->{
            synchronized (room){
                hasTakeout = true;
                log.debug("外賣到了....");
                room.notify();
            }
        },"送外賣的").start();
    }
}

執(zhí)行結(jié)果:送外賣的應(yīng)該叫醒小女但是卻把小南叫醒了

11:31:50.989 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:31:50.994 [小南] DEBUG com.example.test.Test2 - 沒煙,先歇會!
11:31:50.994 [小女] DEBUG com.example.test.Test2 - 有外賣沒?[false]
11:31:50.994 [小女] DEBUG com.example.test.Test2 - 沒外賣,先歇會!
11:31:51.987 [送外賣的] DEBUG com.example.test.Test2 - 外賣到了....
11:31:51.988 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:31:51.988 [小南] DEBUG com.example.test.Test2 - 沒干成活..

notify 只能隨機喚醒一個 WaitSet 中的線程,這時如果有其它線程也在等待,那么就可能喚醒不了正確的線程,稱之為【虛假喚醒】

2.4 問題4

解決方法:改為 notifyAll

new Thread(()->{
    synchronized (room){
        hasTakeout = true;
        log.debug("外賣到了....");
        room.notifyAll();
    }
},"送外賣的").start();

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

11:34:24.789 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:34:24.798 [小南] DEBUG com.example.test.Test2 - 沒煙,先歇會!
11:34:24.798 [小女] DEBUG com.example.test.Test2 - 有外賣沒?[false]
11:34:24.802 [小女] DEBUG com.example.test.Test2 - 沒外賣,先歇會!
11:34:25.794 [送外賣的] DEBUG com.example.test.Test2 - 外賣到了....
11:34:25.794 [小女] DEBUG com.example.test.Test2 - 有外賣沒?[true]
11:34:25.794 [小女] DEBUG com.example.test.Test2 - 有外賣,[true],可以開始干活了
11:34:25.794 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:34:25.795 [小南] DEBUG com.example.test.Test2 - 沒干成活..

從結(jié)果可以看出小女干成活,小南沒有干成活。既然送煙的沒到,小南應(yīng)該繼續(xù)等待才行,等送煙的來了再干活。

用 notifyAll 僅解決某個線程的喚醒問題,但使用 if + wait 判斷僅有一次機會,一旦條件不成立,就沒有重新判斷的機會了

2.5 最終結(jié)果

@Slf4j
public class Test2 {
    private static final Object room = new Object();
    // 是否有煙,默認沒有
    private static boolean hasCigarette = false;
    // 是否有外賣,默認沒有
    static boolean hasTakeout = false;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (room){
                log.debug("有煙沒?[{}]", hasCigarette);
                while (!hasCigarette){
                    log.debug("沒煙,先歇會!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有煙沒?[{}]", hasCigarette);
                if(hasCigarette){
                    log.debug("有煙,[{}],可以開始干活了", hasCigarette);
                }else {
                    log.debug("沒干成活..");
                }
            }
        },"小南").start();

        new Thread(()->{
            synchronized (room){
                log.debug("有外賣沒?[{}]", hasTakeout);
                if(!hasTakeout){
                    log.debug("沒外賣,先歇會!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有外賣沒?[{}]", hasTakeout);
                if(hasTakeout){
                    log.debug("有外賣,[{}],可以開始干活了", hasTakeout);
                }else{
                    log.debug("沒干成活..");
                }
            }
        },"小女").start();
        // 主線程等待1s
        Thread.sleep(1000);
        // 送煙的,喚醒正在睡眠的小南線程
        new Thread(()->{
            synchronized (room){
                hasTakeout = true;
                log.debug("外賣到了....");
                room.notifyAll();
            }
        },"送外賣的").start();
    }
}
@Slf4j
public class Test2 {
    private static final Object room = new Object();
    // 是否有煙,默認沒有
    private static boolean hasCigarette = false;
    // 是否有外賣,默認沒有
    static boolean hasTakeout = false;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            synchronized (room){
                log.debug("有煙沒?[{}]", hasCigarette);
                while (!hasCigarette){
                    log.debug("沒煙,先歇會!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有煙沒?[{}]", hasCigarette);
                if(hasCigarette){
                    log.debug("有煙,[{}],可以開始干活了", hasCigarette);
                }else {
                    log.debug("沒干成活..");
                }
            }
        },"小南").start();

        new Thread(()->{
            synchronized (room){
                log.debug("有外賣沒?[{}]", hasTakeout);
                if(!hasTakeout){
                    log.debug("沒外賣,先歇會!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("有外賣沒?[{}]", hasTakeout);
                if(hasTakeout){
                    log.debug("有外賣,[{}],可以開始干活了", hasTakeout);
                }else{
                    log.debug("沒干成活..");
                }
            }
        },"小女").start();
        // 主線程等待1s
        Thread.sleep(1000);
        // 送煙的,喚醒正在睡眠的小南線程
        new Thread(()->{
            synchronized (room){
                hasTakeout = true;
                log.debug("外賣到了....");
                room.notifyAll();
            }
        },"送外賣的").start();
    }
}

執(zhí)行結(jié)果:當(dāng)沒煙的時候,小南線程繼續(xù)等待,等待下一次判斷有煙的時候再干活

11:38:36.206 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:38:36.212 [小南] DEBUG com.example.test.Test2 - 沒煙,先歇會!
11:38:36.212 [小女] DEBUG com.example.test.Test2 - 有外賣沒?[false]
11:38:36.212 [小女] DEBUG com.example.test.Test2 - 沒外賣,先歇會!
11:38:37.205 [送外賣的] DEBUG com.example.test.Test2 - 外賣到了....
11:38:37.205 [小女] DEBUG com.example.test.Test2 - 有外賣沒?[true]
11:38:37.205 [小女] DEBUG com.example.test.Test2 - 有外賣,[true],可以開始干活了
11:38:37.205 [小南] DEBUG com.example.test.Test2 - 沒煙,先歇會!

使用wait-notify的正確姿勢:

synchronized(lock) {
     while(條件不成立) {
         lock.wait();
     }
}
//另一個線程
synchronized(lock) {
 	lock.notifyAll();
}

調(diào)用wait()和notify()系列方法進行線程通信的要點如下:

(1) 調(diào)用某個同步對象locko的wait()和notify()類型方法前,必須要取得這個鎖對象的監(jiān)視鎖,所以wait()和notify()類型方法必須放在synchronized(locko)同步塊中,如果沒有獲得監(jiān)視鎖,JVM就會報IllegalMonitorStateException異常。

(2) 調(diào)用wait()方法時使用while進行條件判斷,如果是在某種條件下進行等待,對條件的判斷就不能使用if語句做一次性判斷,而是使用while循環(huán)進行反復(fù)判斷。只有這樣才能在線程被喚醒后繼續(xù)檢查wait的條件,并在條件沒有滿足的情況下繼續(xù)等待。

“Java中怎么正確使用wait-notify方法”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

向AI問一下細節(jié)

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

AI