等待和通知機制(wait和notify)

1. 等待和通知機制的實現

wait() 方法

wait() 是 Object 類的方法,它的作用是使當前執行wait方法的線程進行等待,該方法將當前線程置入“預執行隊列”中,並在 wait() 所在的代碼行處停止執行,直到接到通知或者被中斷才能繼續執行。線程必須獲得該對象的對象鎖,即只能在同步方法或者同步方法塊中調用 wait() 方法,在執行 wait() 方法後,當前線程釋放所擁有的對象鎖,如果 wait() 沒有持有對象鎖就執行,會拋出 IllegalMonitorStateException 異常

notify() 方法

notify() 是 Object 類的方法,作用是使停止的線程繼續運行,也要在同步方法或者同步塊中調用。該方法用來通知那些可能等待該對象的對象鎖的其他線程,如果有多個線程等待,則由線程規劃器隨機挑選處一個呈 wait 狀態的線程,對其發出通知 notify,但是被通知的線程不會馬上執行 wait 後面的代碼,因爲使用 notify 的線程不會馬上釋放鎖,所以被通知的線程也不會馬上得到鎖。如果調用 notify 時沒有持有對象鎖,就會拋出 IllegalMonitorStateException 異常

使用 wait() 和 notify() 方法進行測試

class MyThread1 extends Thread {

    private Object lock;

    public MyThread1(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName()
                    + " 開始 wait time = " + System.currentTimeMillis());
            try {
                //wait 使線程 thread1 停止運行
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()
                    + " 結束 wait time = " + System.currentTimeMillis());
        }
    }
}

class MyThread2 extends Thread {

    private Object lock;

    public MyThread2(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName()
                    + " 開始 notify time = " + System.currentTimeMillis());
            //線程 thread2 使停止的 thread1 繼續運行
            lock.notify();
            System.out.println(Thread.currentThread().getName()
                    + " 結束 notify time = " + System.currentTimeMillis());
        }
    }
    
}

public class Test extends Thread {
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        MyThread1 thread1 = new MyThread1(lock);
        thread1.start();
        Thread.sleep(2000);
        MyThread2 thread2 = new MyThread2(lock);
        thread2.start();
    }
    
}

結果是:

AAA 開始 wait time = 1540528981658
BBB 開始 notify time = 1540528983660
BBB 結束 notify time = 1540528983661
AAA 結束 wait time = 1540528983661

簡單分析一下整個過程,線程 AAA 先執行 run() 方法,獲得了 lock 對象鎖,輸出一行之後執行 lock.wait() 方法,表示線程 AAA 釋放對象 lock,然後持有該對象鎖的線程 AAA 進入等待狀態,但是線程 AAA 依然在 synchronized 同步塊中;
由於線程 AAA 停止了,此時線程 BBB 開始執行 run() 方法,獲取 lock 對象鎖,輸出一行之後,調用 lock.notify() 喚醒正在等待對象鎖 lock 的線程 AAA,使其進入就緒狀態,但線程 BBB 並不馬上釋放對象鎖 lock,而是繼續執行自己同步方法中的剩餘方法。只有當線程 BBB 執行完 syncrhonized 同步塊之後,才釋放對象鎖,此時被喚醒的線程 AAA 纔可以重新獲得該對象鎖,然後執行 wait() 方法之後的代碼

整個過程如圖所示,簡單來說,就是線程 AAA 被 wait,然後線程 BBB 使用 notify 喚醒線程 AAA,但是不立即釋放鎖,直到線程 BBB 執行完同步塊之後,才釋放鎖,此時線程 AAA 得到鎖,開始執行 wait 後的方法

有一點需要注意,wait() 方法是使 擁有對象鎖的那個線程暫時等待,而與誰是那個對象鎖沒有關係,就像這個例子中,線程 AAA 擁有 Object 對象的對象鎖 lock,同時在 run() 方法中用對象鎖 lock 調用 wait() 方法,然後對象鎖 lock 被釋放,注意,是線程 AAA 持有的對象鎖 lock被釋放,因爲線程 AAA 在同步塊中,隨即造成的是,線程 AAA 進入等待狀態。這一點很重要!!!

同時,在同步代碼塊中,必須調用獲取的鎖對象的 wait、nofity 或者 notifyAll 方法,在等待線程中,調用 wait() 方法的那個對象一定是 synchronized() 括號裏的那個對象,而在通知線程中,調用 notify() 方法的那個對象也一定是 synchronized 括號裏的那個對象,如果不是的話,會拋出 IllegalMonitorStateException 異常

notify()只能喚醒一個線程

如果有多個線程處於等待狀態,那麼 notify() 方法只能隨機喚醒一個線程,其他沒有沒喚醒的線程依舊處於等待狀態。但是可以多次調用 notity() 方法來隨機喚醒多個線程

//Service2 方法
class Service2 {

    public void testMethod(Object lock) {

        synchronized (lock) {
            System.out.println(Thread.currentThread().getName()
                    + " beg1in wait " + System.currentTimeMillis());
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()
                    + " end wait " + System.currentTimeMillis());
        }

    }

}

創建三個線程 ThreadA6、ThreadB6 和 ThreadC6

class ThreadA6 extends Thread {

    private Object lock;

    public ThreadA6(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        Service2 service2 = new Service2();
        service2.testMethod(lock);
    }

}

class ThreadB6 extends Thread {

    private Object lock;

    public ThreadB6(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        Service2 service2 = new Service2();
        service2.testMethod(lock);
    }

}

class ThreadC6 extends Thread {

    private Object lock;

    public ThreadC6(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        Service2 service2 = new Service2();
        service2.testMethod(lock);
    }
}

隨機喚醒一個正在等待的線程的方法

class NotifyOne extends Thread {

    private Object lock;

    public NotifyOne(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println("NotifyOne");
            lock.notify();
        }
    }
}

多次調用可以隨機喚醒多個正在等待的線程的方法

class NotifyMulti extends Thread {

    private Object lock;

    public NotifyMulti(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println("NotifyMulti");
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
        }
    }
}

測試方法

public class Test2 {

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        ThreadA6 threadA6 = new ThreadA6(lock);
        threadA6.start();
        ThreadB6 threadB6 = new ThreadB6(lock);
        threadB6.start();
        ThreadC6 threadC6 = new ThreadC6(lock);
        threadC6.start();

        Thread.sleep(2000);

        NotifyOne notifyOne = new NotifyOne(lock);
        notifyOne.start();
       	/*NotifyMulti notifyMulti = new NotifyMulti(lock);
        notifyMulti.start();*/
    }

}

結果是:

Thread-0 beg1in wait 1540536524678
Thread-1 beg1in wait 1540536524679
Thread-2 beg1in wait 1540536524679
notifyOne 喚醒了一個線程 1540536526679
Thread-0 end wait 1540536526679

由於只能喚醒一個線程,另外兩個還處於等待狀態的線程因爲沒有被喚醒,就處於永遠等待的狀態了,如果調用後面被註釋的語句,那麼就能喚醒多個線程了

Thread-0 beg1in wait 1540536666626
Thread-2 beg1in wait 1540536666626
Thread-1 beg1in wait 1540536666626
NotifyMulti
Thread-0 end wait 1540536668627
Thread-1 end wait 1540536668627
Thread-2 end wait 1540536668627

notifyAll()可以喚醒多個線程

由於不知道有多少個線程處於等待的狀態,我們也不可能一直不停調用 notify() 方法,這樣會很麻煩,因此可以使用 notifyAll() 方法喚醒全部正在等待的方法

2. 當interrupt方法遇到wait方法

當線程呈 wait() 狀態時,調用線程對象的 interrupt() 方法會出現 InterruptedException 異常

class Service5 {

    public void testMethod(Object lock) {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + " be1gin wait "
                + System.currentTimeMillis());
            try {
                lock.wait();
                System.out.println(Thread.currentThread().getName() + " end wait "
                        + System.currentTimeMillis());
            } catch (InterruptedException e) {
                System.out.println("發生異常...");
                e.printStackTrace();
            }
        }
    }

    public void testMethod2(Object lock) {
        synchronized (lock) {
            System.out.println(Thread.currentThread().getName() + " begin 2");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " end 2");
        }
    }

}

class ThreadB5 extends Thread {

    private Object lock;

    public ThreadB5(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        Service5 service5 = new Service5();
        service5.testMethod2(lock);
    }
}

public class ThreadA5 extends Thread {

    private Object lock;

    public ThreadA5(Object lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        Service5 service5 = new Service5();
        service5.testMethod(lock);
    }

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        ThreadA5 threadA5 = new ThreadA5(lock);
        threadA5.start();
        threadA5.interrupt();
        Thread.sleep(2000);

        ThreadB5 threadB5 = new ThreadB5(lock);
        threadB5.start();
    }
}

結果是:

Thread-0 be1gin wait 1540534325308
發生異常...
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at edu.just.Service5.testMethod(ThreadA5.java:10)
	at edu.just.ThreadA5.run(ThreadA5.java:60)
Thread-1 begin 2
Thread-1 end 2

可以看到,報錯了,同時還執行了 wait() 方法之後的代碼,這是因爲:在執行同步代碼塊的過程中,遇到異常而導致線程終止,鎖也會被釋放,此時如果還有線程持有對象鎖,那麼 wait 後面的代碼將不會執行,而是直接報錯

3. 通知時間

wait(long)方法

如果 wait() 方法裏面帶有參數,表示在一段時間內,如果沒有其他線程對等待的線程進行喚醒,那麼等待的線程在超過這個時間之後會自動喚醒

  1. 如果在規定時間之內就被喚醒,那麼會先執行其他線程的代碼,然後在執行 wait 之後的代碼
  2. 如果在規定直接之外被喚醒,那麼就會先執行 wait 之後代碼,在執行其他線程的代碼
public class MyRunnable {

    private static Object lock = new Object();

    private static Runnable runnable = new Runnable() {
        @Override
        public void run() {
            synchronized (lock) {

                System.out.println(Thread.currentThread().getName()
                        + " wait begin time " + System.currentTimeMillis());
                try {
                    //規定 1s 之後線程自動被喚醒
                    lock.wait(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()
                        + " wait end time " + System.currentTimeMillis());
            }
        }
    };

    private static Runnable runnable1 = new Runnable() {
        @Override
        public void run() {
            synchronized (lock) {

                System.out.println(Thread.currentThread().getName()
                        + " wait begin time2 " + System.currentTimeMillis());
                try {
                    lock.wait(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()
                        + " wait end time " + System.currentTimeMillis());
            }
        }
    };

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(runnable);
        thread.setName("AAA");
        thread.start();
		//在規定時間之內線程 AAA 被喚醒		語句1
        Thread.sleep(1100);
		//在規定時間之外線程 AAA 被喚醒     語句2
//        Thread.sleep(900);
        Thread thread2 = new Thread(runnable1);
        thread2.setName("BBB");
        thread2.start();
    }

}

先把語句2註釋掉,結果是:

AAA wait begin time 1540538435802
AAA wait end time 1540538436802
BBB wait begin time2 1540538436902
BBB wait end time 1540538436903

看到此時線程 AAA 在被線程 BBB 手動喚醒之前就自動喚醒,所以直接執行了 wait 後面的方法

在執行語句2,把語句1註釋,結果是:

AAA wait begin time 1540538528885
BBB wait begin time2 1540538529784
BBB wait end time 1540538529784
AAA wait end time 1540538529885

此時線程 BBB 在線程 AAA 被自動喚醒前就將線程 AAA 喚醒了,此時先執行完線程 BBB 的代碼,在執行線程 AAA wait() 方法後面的代碼

4.wait()和sleep()方法的區別

wait() 方法和 sleep() 方法很類似,下面可以做個對比:

  1. wait 是 Object 類的成員變量,而 sleep 是 Thread 類的靜態方法
  2. 調用 wait 方法前需要先獲取對象鎖,而調用 sleep 方法不需要先獲取對象鎖
  3. 調用 wait 方法的線程需要用 notify 來喚醒,而 sleep 方法必須設置超時值
  4. 線程調用 wait 方法後會先釋放鎖,而 sleep 方法不會釋放鎖

5.參考

《Java多線程編程核心技術》
https://mp.weixin.qq.com/s?__biz=MzIxNTQ3NDMzMw==&mid=2247483803&idx=1&sn=d40db47a68287ea10c838d5f2f9a700c&scene=19#wechat_redirect

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