wait,notify,notifyAll詳細介紹

裝載:http://qifuguang.me/2015/10/23/wait-notify-notifyAll%E8%AF%A6%E7%BB%86%E4%BB%8B%E7%BB%8D/

概述

wait,notify和notifyAll方法是Object類的成員函數,所以Java的任何一個對象都能夠調用這三個方法。這三個方法主要是用於線程間通信,協調多個線程的運行。

wait函數

調用線程的sleep,yield方法時,線程並不會讓出對象鎖,wait卻不同。

wait函數必須在同步代碼塊中調用(也就是當前線程必須持有對象的鎖),他的功能是這樣的:

我累了,休息一會兒,對象的鎖你們拿去用吧,CPU也給你們。

調用了wait函數的線程會一直等待,直到有其他線程調用了同一個對象的notify或者notifyAll方法才能被喚醒,需要注意的是:被喚醒並不代表立即獲得對象的鎖。也就是說,一個線程調用了對象的wait方法後,他需要等待兩件事情的發生:

  1. 有其他線程調用同一個對象的notify或者notifyAll方法(調用notify/notifyAll方法之前)
  2. 被喚醒之後重新獲得對象的鎖(調用notify/notifyAll方法之後)

才能繼續往下執行後續動作。

如果一個線程調用了某個對象的wait方法,但是後續並沒有其他線程調用該對象的notify或者notifyAll方法,則該線程將會永遠等下去…

notify和notifyAll方法

notofy/notifyAll方法也必須在同步代碼塊中調用(也就是調用線程必須持有對象的鎖),他們的功能是這樣的:

女士們,先生們請注意,鎖的對象我即將用完,請大家醒醒,準備一下,馬上你們就能使用鎖了。

不同的是,notify方法只會喚醒一個正在等待的線程(至於喚醒誰,不確定!),而notifyAll方法會喚醒所有正在等待的線程。還有一點需要特別強調:調用notify和notifyAll方法後,當前線程並不會立即放棄鎖的持有權,而必須要等待當前同步代碼塊執行完纔會讓出鎖。

如果一個對象之前沒有調用wait方法,那麼調用notify方法是沒有任何影響的。

小試牛刀

下面我們舉例子來鞏固上面講到的理論知識,下面的代碼創建了兩個線程,Thread1在同步代碼塊中調用wait,Thread2在同步代碼塊中調用notify:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.winwill.test;

/**
 * @author winwill2012
 * @date 15/8/14 16:37
 */
public class Test {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        new Thread(new Thread1()).start();
        new Thread(new Thread2()).start();
    }

    static class Thread1 implements Runnable {

        @Override
        public void run() {
            System.out.println("Thread1 start...");
            synchronized (lock) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Thread1 stop...");
        }
    }

    static class Thread2 implements Runnable {

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

運行結果如下:

Thread1 start…
Thread2 start…
Thread2 stop…
Thread1 stop…

從上面的例子可以證實上面說到的一個結論:線程調用notify方法後並不會讓出鎖,而必須等待同步代碼塊執行完畢之後再讓出,可以看到執行結果中Thread2的開始和結束是成對挨着出現的。

總結

這三個函數的相互通信可以做很多事情,比如常見的生產者-消費者模式,生產者要往隊列裏面生產東西,就必須等待隊列有空間,同樣的,消費者要同隊列裏面消費東西,就必須等待隊列裏有東西。使用wait,notify,notifyAll方法可以協調生產者和消費者之間的行爲。在JDK1.4之後出現了一個Condition類,這個類也能夠實現相同的功能,並且一般建議使用Condition替代wait,notify,notifyAll家族,實現更安全的線程間通信功能,比如ArrayBlockingQueue就是使用Condition實現阻塞隊列的。


下面是筆者自己補充的內容:下面例子中synObj充當鎖,synObj.wait()後,該鎖被釋放由t2獲得,在執行完synObj.notify()後該鎖由t1獲得

final Object synObj = new Object();		
Thread t1 = new Thread(new Runnable() {
	@Override
	public void run() {
		synchronized(synObj) {
			System.out.println("T1獲取synObj的對象監視器,開始執行同步塊");
			try {
				TimeUnit.MINUTES.sleep(1);
				System.out.println("T1在 wait()時掛起了");
				synObj.wait();
				System.out.println("T1被T2喚醒後並重新獲得synObj的對象監視器,繼續執行");						
			}catch(InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("T1獲取synObj的對象監視器,結束同步塊");
		}				
	};
});
t1.start();


Thread t2 = new Thread(new Runnable() {
	@Override
	public void run() {
		System.out.println("T2啓動,但是因爲T1佔用了synObj的對象監視器,則等待T1執行synObj.wait來釋放它");
		synchronized(synObj) {
			try {
				System.out.println("在T1執行synObj.wait後,T2獲取synObj的對象監視器,進入同步塊");
				synObj.notify();
				System.out.println("T2執行synObj.notify(),T1被喚醒,但T2還在同步塊中,沒有釋放synObj的對象監視器,T1等待synObj的對象監視器");
				TimeUnit.MINUTES.sleep(1);
				System.out.println("T2結束同步塊,釋放synObj的對象監視器,T1獲取到synObj的對象監視器,並執行wait後面的操作");
			}catch(InterruptedException e) {
				e.printStackTrace();
			}
		}				
	};
});
t2.start();


發佈了21 篇原創文章 · 獲贊 28 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章