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

  
版權聲明:本文爲博主原創文章,轉載請註明出處:http://blog.csdn.net/lingzhm
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輸出。

  1. class NumberPrint implements Runnable{  
  2.     private int number;  
  3.     public byte res[];  
  4.     public static int count = 5;  
  5.     public NumberPrint(int number, byte a[]){  
  6.         this.number = number;  
  7.         res = a;  
  8.     }  
  9.     public void run(){  
  10.         synchronized (res){  
  11.             while(count– > 0){  
  12.                 try {  
  13.                     res.notify();//喚醒等待res資源的線程,把鎖交給線程(該同步鎖執行完畢自動釋放鎖)  
  14.                     System.out.println(” ”+number);  
  15.                     res.wait();//釋放CPU控制權,釋放res的鎖,本線程阻塞,等待被喚醒。  
  16.                     System.out.println(”——線程”+Thread.currentThread().getName()+“獲得鎖,wait()後的代碼繼續運行:”+number);  
  17.                 } catch (InterruptedException e) {  
  18.                     // TODO Auto-generated catch block  
  19.                     e.printStackTrace();  
  20.                 }  
  21.             }//end of while  
  22.             return;  
  23.         }//synchronized  
  24.           
  25.     }  
  26. }  
  27. public class WaitNotify {  
  28.     public static void main(String args[]){  
  29.         final byte a[] = {0};//以該對象爲共享資源  
  30.         new Thread(new NumberPrint((1),a),“1”).start();  
  31.         new Thread(new NumberPrint((2),a),“2”).start();  
  32.     }  
  33. }  
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.  1  
  2.  2  
  3. ——線程1獲得鎖,wait()後的代碼繼續運行:1  
  4.  1  
  5. ——線程2獲得鎖,wait()後的代碼繼續運行:2  
  6.  2  
  7. ——線程1獲得鎖,wait()後的代碼繼續運行:1  
  8.  1  
  9. ——線程2獲得鎖,wait()後的代碼繼續運行:2  
 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控制權。

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