啃碎併發(八):深刻分析wait¬ify原理

0 前言

上一節講了Synchronized關鍵詞的原理與優化分析,而配合Synchronized使用的另外兩個關鍵詞wait&notify是本章講解的重點。最簡單的東西,每每包含了最複雜的實現,由於須要爲上層的存在提供一個穩定的基礎,Object做爲Java中全部對象的基類,其存在的價值不言而喻,其中wait&notify方法的實現多線程協做提供了保證java

1 源碼

今天咱們要學習或者說分析的是 Object 類中的 wait&notify 這兩個方法,其實說是兩個方法,這兩個方法包括他們的重載方法一共有 5 個,而 Object 類中一共才 12 個方法,可見這 2 個方法的重要性。咱們先看看 JDK 中的代碼:bash

public final native void notify();

public final native void notifyAll();

public final void wait() throws InterruptedException {
    wait(0);
}

public final native void wait(long timeout) throws InterruptedException;

public final void wait(long timeout, int nanos) throws InterruptedException {
    if (timeout < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
                            "nanosecond timeout value out of range");
    }
    // 此處對於納秒的處理不精準,只是簡單增長了1毫秒,
    if (nanos > 0) {
        timeout++;
    }

    wait(timeout);
}
複製代碼

就是這五個方法。其中有 3 個方法是 native 的,也就是由虛擬機本地的 c 代碼執行的。有 2 個 wait 重載方法最終仍是調用了 wait(long) 方法。多線程

  1. wait方法:wait是要釋放對象鎖,進入等待池。既然是釋放對象鎖,那麼確定是先要得到鎖。因此wait必需要寫在synchronized代碼塊中,不然會報異常。ide

  2. notify方法:也須要寫在synchronized代碼塊中,調用對象的這兩個方法也須要先得到該對象的鎖。notify,notifyAll,喚醒等待該對象同步鎖的線程。notify喚醒對象等待池中的一個線程,將這個線程放入該對象的鎖池中。對象的鎖池中線程能夠去競爭獲得對象鎖,而後開始執行學習

    1. 若是是經過notify來喚起的線程,那先進入wait的線程會先被喚起來,並不是隨機喚醒;
    2. 若是是經過nootifyAll喚起的線程,默認狀況是最後進入的會先被喚起來,即LIFO的策略;

    另外一點,notify,notifyAll調用時並不會釋放對象鎖。好比如下代碼:優化

    public void test() {
        Object object = new Object();
        synchronized (object){
            object.notifyAll();
            while (true){
             
            }
        }
    }
    複製代碼

    雖然調用了notifyAll,可是緊接着進入了一個死循環。致使一直不能出臨界區,一直不能釋放對象鎖。因此,即便它把全部在等待池中的線程都喚醒放到了對象的鎖池中,可是鎖池中的全部線程都不會運行,由於他們都拿不到鎖ui

2 用法

簡單示例:spa

public class WaitNotifyCase {
    public static void main(String[] args) {
        final Object lock = new Object();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread A is waiting to get lock");
                synchronized (lock) {
                    try {
                        System.out.println("thread A get lock");
                        TimeUnit.SECONDS.sleep(1);
                        System.out.println("thread A do wait method");
                        lock.wait();
                        System.out.println("wait end");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread B is waiting to get lock");
                synchronized (lock) {
                    System.out.println("thread B get lock");
                    try {
                        TimeUnit.SECONDS.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lock.notify();
                    System.out.println("thread B do notify method");
                }
            }
        }).start();
    }
}
複製代碼

執行結果:線程

thread A is waiting to get lock
thread A get lock
thread B is waiting to get lock
thread A do wait method
thread B get lock
thread B do notify method
wait end
複製代碼

前提:必須由同一個lock對象調用wait、notify方法code

  1. 當線程A執行wait方法時,該線程會被掛起;
  2. 當線程B執行notify方法時,會喚醒一個被掛起的線程A;

lock對象、線程A和線程B三者是一種什麼關係?根據上面的結論,能夠想象一個場景:

  1. lock對象維護了一個等待隊列list;
  2. 線程A中執行lock的wait方法,把線程A保存到list中;
  3. 線程B中執行lock的notify方法,從等待隊列中取出線程A繼續執行;
相關文章
相關標籤/搜索