標籤:
上述例題無條件的阻塞了其他線程非同步訪問某個方法。Java對象中隱式管程的應用是很強大的,但是你可以通過處理序間通訊達到更微妙的境界。這在Java中是尤為簡單的。
像前面所討論過的,多線程通過把任務分成離散的和合乎邏輯的單元代替了事件迴圈程式。線程還有第二優點:它遠離了輪詢。輪詢通常由重複監測條件的迴圈實現。一旦條件成立,就要採取適當的行動。這浪費了CPU時間。舉例來說,考慮經典的序列問題,當一個線程正在產生資料而另一個程式正在消費它。為使問題變得更有趣,假設資料產生器必須等待消費者完成工作才能產生新的資料。在輪詢系統,消費者在等待生產者產生資料時會浪費很多CPU周期。一旦生產者完成工作,它將啟動輪詢,浪費更多的CPU時間等待消費者的工作結束,如此下去。很明顯,這種情形不受歡迎。
為避免輪詢,Java包含了通過wait( ),notify( )和notifyAll( )方法實現的一個處理序間通訊機制。這些方法在對象中是用final方法實現的,所以所有的類都含有它們。這三個方法僅在synchronized方法中才能被調用。儘管這些方法從電腦科學遠景方向上來說具有概念的高度先進性,實際中用起來是很簡單的:
- wait( ) 告知被調用的線程放棄管程進入睡眠直到其他線程進入相同管程並且調用notify( )。
- notify( ) 恢複相同對象中第一個調用 wait( ) 的線程。
- notifyAll( ) 恢複相同對象中所有調用 wait( ) 的線程。具有最高優先順序的線程最先運行。
這些方法在Object中被聲明,如下所示:
final void wait( ) throws InterruptedException
final void notify( )
final void notifyAll( )
wait( )存在的另外的形式允許你定義等待時間。
下面的例子程式錯誤的實行了一個簡單生產者/消費者的問題。它由四個類組成:Q,設法獲得同步的序列;Producer,產生排隊的線程對象;Consumer,消費序列的線程對象;以及PC,建立單個Q,Producer,和Consumer的小類。
1 // An incorrect implementation of a producer and consumer. 2 class Q { 3 int n; 4 synchronized int get() { 5 System.out.println("Got: " + n); 6 return n; 7 } 8 synchronized void put(int n) { 9 this.n = n;10 System.out.println("Put: " + n);11 }12 }13 class Producer implements Runnable {14 Q q;15 Producer(Q q) {16 this.q = q;17 new Thread(this, "Producer").start();18 }19 public void run() {20 int i = 0;21 while(true) {22 q.put(i++);23 }24 }25 }26 class Consumer implements Runnable {27 Q q;28 Consumer(Q q) {29 this.q = q;30 new Thread(this, "Consumer").start();31 }32 public void run() {33 while(true) {34 q.get();35 }36 }37 }38 class PC {39 public static void main(String args[]) {40 Q q = new Q();41 new Producer(q);42 new Consumer(q);43 System.out.println("Press Control-C to stop.");44 }45 }
儘管Q類中的put( )和get( )方法是同步的,沒有東西阻止生產者超越消費者,也沒有東西阻止消費者消費同樣的序列兩次。這樣,你就得到下面的錯誤輸出(輸出將隨處理器速度和裝載的任務而改變):
Put: 1
Got: 1
Got: 1
Got: 1
Got: 1
Got: 1
Put: 2
Put: 3
Put: 4
Put: 5
Put: 6
Put: 7
Got: 7
生產者產生1後,消費者依次獲得同樣的1五次。生產者在繼續產生2到7,消費者沒有機會獲得它們。
用Java正確的編寫該程式是用wait( )和notify( )來對兩個方向進行標誌,如下所示:
1 // A correct implementation of a producer and consumer. 2 class Q { 3 int n; 4 boolean valueSet = false; 5 synchronized int get() { 6 if(!valueSet) 7 try { 8 wait(); 9 } catch(InterruptedException e) {10 System.out.println("InterruptedException caught");11 }12 System.out.println("Got: " + n);13 valueSet = false;14 notify();15 return n;16 }17 synchronized void put(int n) {18 if(valueSet)19 try {20 wait();21 } catch(InterruptedException e) {22 System.out.println("InterruptedException caught");23 }24 this.n = n;25 valueSet = true;26 System.out.println("Put: " + n);27 notify();28 }29 }30 class Producer implements Runnable {31 Q q;32 Producer(Q q) {33 this.q = q;34 new Thread(this, "Producer").start();35 }36 public void run() {37 int i = 0;38 while(true) {39 q.put(i++);40 }41 }42 }43 class Consumer implements Runnable {44 Q q;45 Consumer(Q q) {46 this.q = q;47 new Thread(this, "Consumer").start();48 }49 public void run() {50 while(true) {51 q.get();52 }53 }54 }55 class PCFixed {56 public static void main(String args[]) {57 Q q = new Q();58 new Producer(q);59 new Consumer(q);60 System.out.println("Press Control-C to stop.");61 }62 }
內部get( ), wait( )被調用。這使執行掛起直到Producer 告知資料已經預備好。這時,內部get( ) 被恢複執行。擷取資料後,get( )調用notify( )。這告訴Producer可以向序列中輸入更多資料。在put( )內,wait( )掛起執行直到Consumer取走了序列中的項目。當執行再繼續,下一個資料項目被放入序列,notify( )被調用,這通知Consumer它應該移走該資料。
下面是該程式的輸出,它清楚的顯示了同步行為:
Put: 1
Got: 1
Put: 2
Got: 2
Put: 3
Got: 3
Put: 4
Got: 4
Put: 5
Got: 5
系列文章:
Java知多少(上)
Java知多少(39)interface介面
Java知多少(40)介面和抽象類別的區別
Java知多少(41)泛型詳解
Java知多少(42)泛型萬用字元和型別參數的範圍
Java知多少(43)異常處理基礎
Java知多少(44)異常類型
Java知多少(45)未被捕獲的異常
Java知多少(46)try和catch的使用
Java知多少(47)多重catch語句的使用
Java知多少(48)try語句的嵌套
Java知多少(49)throw:異常的拋出
Java知多少(50)Java throws子句
Java知多少(51)finally
Java知多少(52)內建異常
Java知多少(53)使用Java建立自己的異常子類
Java知多少(54)斷言詳解
Java知多少(55)線程
Java知多少(56)執行緒模式Java知多少(57)主線程Java知多少(58)線程Runnable介面和Thread類詳解Java知多少(59)建立多線程Java知多少(60)isAlive()和join()的使用Java知多少(61)線程優先順序Java知多少(62)線程同步
Java知多少(63)線程間通訊