首頁 > 軟體

Java多執行緒wait()和notify()方法詳細圖解

2022-11-01 14:02:27

一、執行緒間等待與喚醒機制

wait()和notify()是Object類的方法,用於執行緒的等待與喚醒,必須搭配synchronized 鎖來使用。

多執行緒並行的場景下,有時需要某些執行緒先執行,這些執行緒執行結束後其他執行緒再繼續執行。

比如: 一個長跑比賽,裁判員要等跑步運動員衝線了才能宣判比賽結束,那裁判員執行緒就得等待所有的運動員執行緒執行結束後,再喚醒這個裁判執行緒。

二、等待方法wait()

wait 做的事情:

  • 使當前執行程式碼的執行緒進行等待. (把執行緒放到等待佇列中)
  • 釋放當前的鎖
  • 滿足一定條件時被喚醒, 重新嘗試獲取這個鎖.

wait 要搭配 synchronized 來使用. 脫離 synchronized 使用 wait 會直接丟擲異常.

wait 結束等待的條件:

  • 其他執行緒呼叫該物件的 notify 方法.
  • wait 等待時間超時 (wait 方法提供一個帶有 timeout 引數的版本, 來指定等待時間).
  • 其他執行緒呼叫該等待執行緒的 interrupted 方法, 導致 wait 丟擲 InterruptedException 異常.

注意事項:

  1. 呼叫wait()方法的前提是首先要獲取該物件的鎖(synchronize物件鎖)
  2. 呼叫wait()方法會釋放鎖,本執行緒進入等待佇列等待被喚醒,被喚醒後不是立即恢復執行,而是進入阻塞佇列競爭鎖

等待方法:

1.痴漢方法,死等,執行緒進入阻塞態(WAITING)直到有其他執行緒呼叫notify方法喚醒

2.等待一段時間,若在該時間內執行緒被喚醒,則繼續執行,若超過相應時間還沒有其他執行緒喚醒此執行緒,此執行緒不再等待,恢復執行。

呼叫wait方法之後:

三、喚醒方法notify()

notify 方法是喚醒等待的執行緒.

  • 方法notify()也要在同步方法或同步塊中呼叫,該方法是用來通知那些可能等待該物件的物件鎖的其它執行緒,對其發出通知notify,並使它們重新獲取該物件的物件鎖。
  • 如果有多個執行緒等待,則有執行緒排程器隨機挑選出一個呈 wait 狀態的執行緒。(並沒有 “先來後到”)
  • 在notify()方法後,當前執行緒不會馬上釋放該物件鎖,要等到執行notify()方法的執行緒將程式執行完,也就是退出同步程式碼塊之後才會釋放物件鎖。

注意事項:

  • notify():隨機喚醒一個處在等待狀態的執行緒。
  • notifyAll():喚醒所有處在等待狀態的執行緒。
  • 無論是wait還是notify方法,都需要搭配synchronized鎖來使用(等待和喚醒,也是需要物件)

四、關於wait和notify內部等待問題(重要)

對於wait和notify方法,其實有一個阻塞佇列也有一個等待佇列

  • 阻塞佇列表示同一時間只有一個執行緒能獲取到鎖,其他執行緒進入阻塞佇列
  • 等待佇列表示呼叫wait (首先此執行緒要獲取到鎖,進入等待佇列,釋放鎖

舉個栗子:

現有如下定義的等待執行緒任務

private static class WaitTask implements Runnable {
        private Object lock;
        public WaitTask(Object lock) {
            this.lock = lock;
        }
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + "準備進入等待狀態");
                // 此執行緒在等待lock物件的notify方法喚醒
                try {
                    lock.wait();
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "等待結束,本執行緒繼續執行");
            }
        }
    }

然後建立三個等待執行緒:

由於同一時間只有一個執行緒(隨機排程)能獲取到synchronized鎖,所以會有兩個執行緒沒競爭到鎖,從而進入了阻塞佇列

這裡假如t2先競爭到了鎖,所以先會阻塞t1和t3:

又由於呼叫wait方法會釋放鎖,呼叫wait方法的執行緒t2就會進入等待佇列,直到被notify喚醒或者超時自動喚醒。

然後此時lock物件已經被釋放了,所以t1和t3 又可以去競爭這個鎖了,就從阻塞佇列裡面競爭鎖。

這裡假如t3 競爭到了鎖,阻塞佇列只剩下t1:

然後t3執行到了wait方法,釋放鎖,然後進入等待佇列

然後重複這些操作~~,最後t1,t2,t3 都進入了等待佇列中,等待notify執行緒喚醒(這裡假設notify要放在這些執行緒start後的好幾秒後,因為notify執行緒也是和這些執行緒並行執行的,所以等待佇列中的執行緒隨時可能被喚醒

重點來了:

在等待佇列中的執行緒,被notify喚醒之後,會直接回到阻塞佇列去競爭鎖!!!而不是直接喚醒~

舉個栗子:

拿notifyAll()來舉例,假如此時等待佇列中有三個執行緒t1,t2,t3,那麼呼叫notifyAll()會直接把它們三個直接從等待佇列中進入到阻塞佇列中:

然後再去競爭這個鎖,去執行wait之後的程式碼~~

五、完整程式碼(僅供測試用)

private static class WaitTask implements Runnable {
        private Object lock;
        public WaitTask(Object lock) {
            this.lock = lock;
        }
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + "準備進入等待狀態");
                // 此執行緒在等待lock物件的notify方法喚醒
                try {
                    lock.wait();
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "等待結束,本執行緒繼續執行");
            }
        }
    }
    private static class NotifyTask implements Runnable {
        private Object lock;
        public NotifyTask(Object lock) {
            this.lock = lock;
        }
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println("準備喚醒");
                // 喚醒所有執行緒(隨機)
                lock.notifyAll();
                System.out.println("喚醒結束");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Object lock2 = new Object();
        // 建立三個等待執行緒
        Thread t1 = new Thread(new WaitTask(lock),"t1");
        Thread t2 = new Thread(new WaitTask(lock),"t2");
        Thread t3 = new Thread(new WaitTask(lock),"t3");
       // 建立一個喚醒執行緒
        Thread notify = new Thread(new NotifyTask(lock2),"notify執行緒");
        t1.start();
        t2.start();
        t3.start();
        ;
        Thread.sleep(100);
        notify.start();
        // 當前正在執行的執行緒數
        Thread.sleep(2000);
        System.out.println(Thread.activeCount() - 1);
    }

六、wait和sleep方法的區別(面試題):

  • wait方法是Object類提供的方法,需要搭配synchronized鎖來使用,呼叫wait方法會釋放鎖,執行緒進入WAITING狀態,等待被其他執行緒喚醒或者超時自動喚醒,喚醒之後的執行緒需要再次競爭synchronized鎖才能繼續執行。
  • sleep方法是Thread類提供的方法,呼叫sleep方法的執行緒進入TIMED_WAITING狀態,不會釋放鎖,時間到自動喚醒。

總結

以上就是多執行緒場景下wait和notify方法的詳解和注意事項了,更多相關多執行緒wait()和notify()方法內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com