1. 程式人生 > >Java wait() notify()方法使用例項講解

Java wait() notify()方法使用例項講解

  1)wait()、notify()和notifyAll()方法是本地方法,並且為final方法,無法被重寫。

  2)呼叫某個物件的wait()方法能讓當前執行緒阻塞,並且當前執行緒必須擁有此物件的monitor(即鎖,或者叫管程)

  3)呼叫某個物件的notify()方法能夠喚醒一個正在等待這個物件的monitor的執行緒,如果有多個執行緒都在等待這個物件的monitor,則只能喚醒其中一個執行緒;

  4)呼叫notifyAll()方法能夠喚醒所有正在等待這個物件的monitor的執行緒;


     在Java中,是沒有類似於PV操作、程序互斥等相關的方法的。JAVA的程序同步是通過synchronized()來實現的,需要說明的是,Java的synchronized()方法類似於作業系統概念中的互斥記憶體塊,在Java中的Object類物件中,都是帶有一個記憶體鎖的,在有執行緒獲取該記憶體鎖後,其它執行緒無法訪問該記憶體,從而實現Java中簡單的同步、互斥操作。明白這個原理,就能理解為什麼synchronized(this)與synchronized(static XXX)的區別了,synchronized就是針對記憶體區塊申請記憶體鎖,this關鍵字代表類的一個物件,所以其記憶體鎖是針對相同物件的互斥操作,而static成員屬於類專有

,其記憶體空間為該類所有成員共有,這就導致synchronized()對static成員加鎖,相當於對類加鎖,也就是在該類的所有成員間實現互斥,在同一時間只有一個執行緒可訪問該類的例項。如果需要線上程間相互喚醒就需要藉助Object類的wait()方法及nofity()方法。

說了這麼一堆,可能似懂非懂,那麼接下來用一個例子來說明問題,用多執行緒實現連續的1,2,1,2,1,2,1,2,1,2輸出。

class NumberPrint implements Runnable{
	private int number;
	public byte res[];
	public static int count = 5;
	public NumberPrint(int number, byte a[]){
		this.number = number;
		res = a;
	}
	public void run(){
		synchronized (res){
			while(count-- > 0){
				try {
					res.notify();//喚醒等待res資源的執行緒,把鎖交給執行緒(該同步鎖執行完畢自動釋放鎖)
					System.out.println(" "+number);
					res.wait();//釋放CPU控制權,釋放res的鎖,本執行緒阻塞,等待被喚醒。
					System.out.println("------執行緒"+Thread.currentThread().getName()+"獲得鎖,wait()後的程式碼繼續執行:"+number);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}//end of while
			return;
		}//synchronized
		
	}
}
public class WaitNotify {
	public static void main(String args[]){
		final byte a[] = {0};//以該物件為共享資源
		new Thread(new NumberPrint((1),a),"1").start();
		new Thread(new NumberPrint((2),a),"2").start();
	}
}
輸出結果:
 1
 2
------執行緒1獲得鎖,wait()後的程式碼繼續執行:1
 1
------執行緒2獲得鎖,wait()後的程式碼繼續執行:2
 2
------執行緒1獲得鎖,wait()後的程式碼繼續執行:1
 1
------執行緒2獲得鎖,wait()後的程式碼繼續執行:2

下面解釋為什麼會出現這樣的結果:

首先1、2號執行緒啟動,這裡假設1號執行緒先執行run方法獲得資源(實際上是不確定的),獲得物件a的鎖,進入while迴圈(用於控制輸出幾輪):

1、此時物件呼叫它的喚醒方法notify(),意思是這個同步塊執行完後它要釋放鎖,把鎖交給等待a資源的執行緒;

2、輸出1;

3、該物件執行等待方法,意思是此時此刻起擁有這個物件鎖的執行緒(也就是這裡的1號執行緒)釋放CPU控制權,釋放鎖,並且執行緒進入阻塞狀態,後面的程式碼暫時不執行,因未執行完同步塊,所以1也沒起作用;

4、在這之前的某時刻執行緒2執行run方法,但苦於沒有獲得a物件的鎖,所以無法繼續執行,但3步驟之後,它獲得了a的鎖,此時執行a的喚醒方法notify(),同理,意思是這個同步塊執行完後它要釋放鎖,把鎖交給等待a資源的執行緒;

5、輸出2;

6、執行a的等待方法,意思是此時此刻起擁有這個物件鎖的執行緒(也就是這裡的2號執行緒)釋放CPU控制權,釋放鎖,並且執行緒進入阻塞狀態,後面的程式碼暫時不執行,因未執行完同步塊,所以2號執行緒的4步驟的喚醒方法也沒起作用;

7、此時1號執行緒執行到3步驟,發現物件鎖沒有被使用,所以繼續執行3步驟中wait方法後面的程式碼,於是輸出:------執行緒1獲得鎖,wait()後的程式碼繼續執行:1;

8、此時while迴圈滿足條件,繼續執行,所以,再執行1號執行緒的喚醒方法,意思是這個同步塊執行完後它要釋放鎖;

9、輸出1;

10、執行等待方法,執行緒1阻塞,釋放資源鎖;

11、此時執行緒2又獲得了鎖,執行到步驟6,繼續執行wait方法後面的程式碼,所以輸出:------執行緒2獲得鎖,wait()後的程式碼繼續執行:2;

12、繼續執行while迴圈,輸出2;

··· ···

通過上述步驟,相信大家已經明白這兩個方法的使用了,但該程式還存在一個問題,當while迴圈不滿足條件時,肯定會有執行緒還在等待資源,所以主執行緒一直不會終止。當然這個程式的目的僅僅為了給大家演示這兩個方法怎麼用。

 總結:

    wait()方法與notify()必須要與synchronized(resource)一起使用。也就是wait與notify針對已經獲取了resource鎖的執行緒進行操作,從語法角度來說就是Obj.wait(),Obj.notify必須在synchronized(Obj){...}語句塊內。從功能上來說wait()執行緒在獲取物件鎖後,主動釋放CPU控制權,主動釋放物件鎖,同時本執行緒休眠。直到有其它執行緒呼叫物件的notify()喚醒該執行緒,才能繼續獲取物件鎖,並繼續執行。相應的notify()就是對物件鎖的釋放操作。【因此,我們可以發現,wait和notify方法均可釋放物件的鎖,但wait同時釋放CPU控制權,即它後面的程式碼停止執行,執行緒進入阻塞狀態,而notify方法不立刻釋放CPU控制權,而是在相應的synchronized(){}語句塊執行結束,再自動釋放鎖】釋放鎖後,JVM會在等待resoure的執行緒中選取一執行緒,賦予其物件鎖,喚醒執行緒,繼續執行。這樣就提供了線上程間同步、喚醒的操作。Thread.sleep()與Object.wait()二者都可以暫停當前執行緒,釋放CPU控制權,主要的區別在於Object.wait()在釋放CPU同時,釋放了物件鎖的控制,而在同步塊中的Thread.sleep()方法並不釋放鎖,僅釋放CPU控制權。