wait()和notify()詳解

等待通知機制基本用法:

public class Test {

    public static void main(String[] args) throws InterruptedException {

        Thread waitThread = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (Test.class){
                    System.out.println(Thread.currentThread().getName() + " is holding lock...");
                    try {
                        Thread.currentThread().sleep(1000);
                        System.out.println();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        System.out.println("wait() will release lock");
                        Test.class.wait();
                        System.out.println(Thread.currentThread().getName() + " need hold the lock again, and then to continue...");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " will release lock again");
                }
            }
        }, "waitThread");

        Thread notifyThread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " blocked.... for the lock");
                synchronized (Test.class){
                    System.out.println(Thread.currentThread().getName() + " is holding lock...");
                    Test.class.notify();
                    try {
                        Thread.currentThread().sleep(1000);
                        System.out.println();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " will release lock");
                }
            }
        }, "notifyThread");

        waitThread.start();
        Thread.sleep(100);
        notifyThread.start();

    }

}


輸出結果:
waitThread is holding lock...
notifyThread blocked.... for the lock

wait() will release lock
notifyThread is holding lock...

notifyThread will release lock
waitThread need hold the lock again, and then to continue...
waitThread will release lock again

Process finished with exit code 0

 

總結:

  1. wait()和notify()方法調用的前提是,二者所在的線程體中(即run()方法體)都需要先通過synchronized獲取對象的鎖;
  2. 當waitThread線程調用wait()方法後,會釋放鎖對象線程由RUNNING狀態轉向WAITING狀態,即該線程進入等待隊列中;(記住這三點)
  3. wait()方法釋放鎖對象之前,notifyThread線程會阻塞在synchronzied獲取鎖對象的位置;而當wait()釋放鎖後,notifyThread線程會由阻塞狀態嘗試競爭鎖,並拿到鎖,開始執行同步塊,當執行到notify()方法後,會將waitThread線程從等待隊列中移到同步隊列中,即notify方法會觸發waitThread線程由WAITING非競爭狀態轉化爲BLOCKED狀態
  4. 即,notify()方法只是會觸發wait()所在的線程由非競爭的等待隊列進入鎖競爭的同步隊列由WAITING狀態轉向BLOCKED狀態;想要執行wait()方法後續代碼的前提是waitThread需要先競爭到鎖對象,而由於notify()所在的同步塊目前正持有鎖,所以在notify從方法剛被調用(觸發waitThread參與競爭)到所在同步塊釋放鎖(釋放waitThread需要的東西)這個過程裏,waitThread只能一直阻塞

 

等待通知機制:

  1. 等待線程獲取到對象的鎖,調用wait()方法,放棄鎖,進入等待隊列
  2. 通知線程獲取到對象的鎖,調用對象的notify()方法
  3. 等待線程接受到通知,從等待隊列移到同步隊列,進入阻塞狀態
  4. 通知線程釋放鎖後,等待線程獲取到鎖繼續執行

從上面敘述可以看出,不管是notifyThread競爭鎖,還是notify之後,waitThread需要重新進入同步隊列,重新競爭鎖,,最後都需要在阻塞狀態下競爭同一個鎖對象,,因此,等待/通知機制仍依託於同步機制


使用常見細節:

我們做個試驗,嘗試在notify方法調用後,執行一個while循環,不讓其同步塊執行完,這樣的話,wait()雖然接到了從waiting到blocked的提醒,但是仍然獲不到鎖,沒法繼續執行後續代碼:

public class Test {

    public static void main(String[] args) throws InterruptedException {

        Thread waitThread = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (Test.class){
                    try {
                        System.out.println(Thread.currentThread().getName() + " hold lock");
                        Test.class.wait();
                        System.out.println(Thread.currentThread().getName() + " hold lock again!!!");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "waitThread");

        Thread notifyThread = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (Test.class){
                    System.out.println(Thread.currentThread().getName() + " hold lock");
                    Test.class.notify();
                    while (true);
                }
            }
        }, "notifyThread");

        waitThread.start();
        Thread.sleep(100);
        notifyThread.start();

    }

}


輸出結果:
waitThread hold lock
notifyThread hold lock

從輸出結果可知,雖然notify()通知了wait()方法,但是由於notify所在同步塊存在死循環,也就是一直沒法釋放wait()需要的鎖,所以waitThread線程一直會阻塞下去;


線程狀態切換過程

接下來,我們通過getState方法獲取線程狀態,來更直觀的理解wait和notify機制對線程狀態改變的過程:

import org.apache.poi.ss.formula.functions.T;

public class Test {

    private static Thread waitThread;

    public static void main(String[] args) throws InterruptedException {

        waitThread = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (Test.class){
                    try {
                        System.out.println(Thread.currentThread().getName() + " hold lock");
                        Test.class.wait();
                        System.out.println(Thread.currentThread().getName() + " hold lock again!!!");
                        for (int i=0; i<5;i++){
                            try {
                                Thread.currentThread().sleep(1000);
                                System.out.println(Thread.currentThread().getName() + "'s state after hold lock again: "
                                        + Thread.currentThread().getState());
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "waitThread");

        Thread notifyThread = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (Test.class){
                    System.out.println(Thread.currentThread().getName() + " hold lock");
                    for (int i=0; i<5;i++){
                        try {
                            Thread.currentThread().sleep(1000);
                            System.out.println(waitThread.getName() + "'s state before notify: " + waitThread.getState());
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                    }
                    System.out.println("----------------------------------------");
                    Test.class.notify();
                    for (int i=0; i<5;i++){
                        try {
                            Thread.currentThread().sleep(1000);
                            System.out.println(waitThread.getName() + "'s state after notify: " + waitThread.getState());
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("----------------------------------------");
                }
            }
        }, "notifyThread");

        waitThread.start();
        Thread.sleep(100);
        notifyThread.start();

    }

}



輸出結果:
waitThread hold lock
notifyThread hold lock
waitThread's state before notify: WAITING
waitThread's state before notify: WAITING
waitThread's state before notify: WAITING
waitThread's state before notify: WAITING
waitThread's state before notify: WAITING
----------------------------------------
waitThread's state after notify: BLOCKED
waitThread's state after notify: BLOCKED
waitThread's state after notify: BLOCKED
waitThread's state after notify: BLOCKED
waitThread's state after notify: BLOCKED
----------------------------------------
waitThread hold lock again!!!
waitThread's state after hold lock again: RUNNABLE
waitThread's state after hold lock again: RUNNABLE
waitThread's state after hold lock again: RUNNABLE
waitThread's state after hold lock again: RUNNABLE
waitThread's state after hold lock again: RUNNABLE

Process finished with exit code 0

等待/通知的經典範式:

該範式分爲兩部分,分別針對等待方(消費者)和通知方(生產者)。

等待方遵循如下原則。

  • 獲取對象的鎖。
  • 如果條件不滿足,那麼調用對象的wait()方法,被通知後仍要檢查條件。
  • 條件滿足則執行對應的邏輯。
// 對應的僞代碼如下:
synchronized(對象) {

    while(條件不滿足) {

        對象.wait();

    }
    
    對應的處理邏輯

}  

理解說明:
while在這裏是有意義的,當notify的同步塊釋鎖之後,
wait()競爭得到鎖,進入第二輪循環,若此時條件仍不滿足,
那麼wait()被第二次執行,由於這次執行之前,當前線程仍持有鎖,
所以wait有鎖可釋,不會報錯,釋完鎖之後,當前線程就由RUNNING轉向WAITING,
線程進入等待隊列,等待下一次notify通知.

通知方遵循如下原則:

  • 獲得對象的鎖。
  • 改變條件。
  • 通知所有等待在對象上的線程。
// 對應的僞代碼如下:
synchronized(對象) {

    // 改變條件得位置不重要 ,只要在同步塊執行之前(釋鎖之前),
    // 因爲wait只有在notify釋鎖後纔有機會加鎖,進而響應條件的變化
    改變條件

    對象.notifyAll();

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章