Java併發教程-5保護塊(Guarded Blocks)

多線程之間常常須要協同工做,最多見的方式是使用Guarded Blocks,它循環檢查一個條件(一般初始值爲true),直到條件發生變化才跳出循環繼續執行。在使用Guarded Blocks時有如下幾個步驟須要注意: 

假設guardedJoy()方法必需要等待另外一線程爲共享變量joy設值才能繼續執行。那麼理論上能夠用一個簡單的條件循環來實現,但在等待過程當中guardedJoy方法不停的檢查循環條件其實是一種資源浪費。 

Java代碼 
  1. public void guardedJoy() {  
  2.     // Simple loop guard. Wastes  
  3.     // processor time. Don't do this!  
  4.     while(!joy) {}  
  5.     System.out.println("Joy has been achieved!");  
  6. }  


更加高效的方法是調用Object.wait將當前線程掛起,直到有另外一線程發起事件通知(儘管通知的事件不必定是當前線程等待的事件)。 

Java代碼 
  1. public synchronized void guardedJoy() {  
  2.     // This guard only loops once for each special event, which may not  
  3.     // be the event we're waiting for.  
  4.     while(!joy) {  
  5.         try {  
  6.             wait();  
  7.         } catch (InterruptedException e) {}  
  8.     }  
  9.     System.out.println("Joy and efficiency have been achieved!");  
  10. }  


注意:必定要在循環裏面調用wait方法,不要想固然的認爲線程喚醒後循環條件必定發生了改變。 

和其餘能夠暫停線程執行的方法同樣,wait方法會拋出InterruptedException,在上面的例子中,由於咱們關心的是joy的值,因此忽略了InterruptedException。 

爲何guardedJoy是synchronized方法?假設d是用來調用wait的對象,當一個線程調用d.wait,它必需要擁有d的內部鎖(不然會拋出異常),得到d的內部鎖的最簡單方法是在一個synchronized方法裏面調用wait。 

當一個線程調用wait方法時,它釋放鎖並掛起。而後另外一個線程請求並得到這個鎖並調用 Object.notifyAll 通知全部等待該鎖的線程。 

Java代碼 
  1. public synchronized notifyJoy() {  
  2.     joy = true;  
  3.     notifyAll();  
  4. }  


當第二個線程釋放這個該鎖後,第一個線程再次請求該鎖,從wait方法返回並繼續執行。 

注意:還有另一個通知方法,notify(),它只會喚醒一個線程。但因爲它並不容許指定哪個線程被喚醒,因此通常只在大規模併發應用(即系統有大量類似任務的線程)中使用。由於對於大規模併發應用,咱們其實並不關心哪個線程被喚醒。 

如今咱們使用Guarded blocks建立一個生產者/消費者應用。這類應用須要在兩個線程之間共享數據:生產者生產數據,消費者使用數據。兩個線程經過共享對象通訊。在這裏,線程協同工做的關鍵是:生產者發佈數據以前,消費者不可以去讀取數據;消費者沒有讀取舊數據前,生產者不能發佈新數據。 

在下面的例子中,數據經過 Drop 對象共享的一系列文本消息: 

Java代碼 
  1. public class Drop {  
  2.     // Message sent from producer  
  3.     // to consumer.  
  4.     private String message;  
  5.     // True if consumer should wait  
  6.     // for producer to send message,  
  7.     // false if producer should wait for  
  8.     // consumer to retrieve message.  
  9.     private boolean empty = true;  
  10.   
  11.     public synchronized String take() {  
  12.         // Wait until message is  
  13.         // available.  
  14.         while (empty) {  
  15.             try {  
  16.                 wait();  
  17.             } catch (InterruptedException e) {}  
  18.         }  
  19.         // Toggle status.  
  20.         empty = true;  
  21.         // Notify producer that  
  22.         // status has changed.  
  23.         notifyAll();  
  24.         return message;  
  25.     }  
  26.   
  27.     public synchronized void put(String message) {  
  28.         // Wait until message has  
  29.         // been retrieved.  
  30.         while (!empty) {  
  31.             try {  
  32.                 wait();  
  33.             } catch (InterruptedException e) {}  
  34.         }  
  35.         // Toggle status.  
  36.         empty = false;  
  37.         // Store message.  
  38.         this.message = message;  
  39.         // Notify consumer that status  
  40.         // has changed.  
  41.         notifyAll();  
  42.     }  
  43. }  


Producer 是生產者線程,發送一組消息,字符串DONE表示全部消息都已經發送完成。爲了模擬現實狀況,生產者線程還會在消息發送時隨機的暫停。 

Java代碼 
  1. import java.util.Random;  
  2.   
  3. public class Producer implements Runnable {  
  4.     private Drop drop;  
  5.   
  6.     public Producer(Drop drop) {  
  7.         this.drop = drop;  
  8.     }  
  9.   
  10.     public void run() {  
  11.         String importantInfo[] = {  
  12.             "Mares eat oats",  
  13.             "Does eat oats",  
  14.             "Little lambs eat ivy",  
  15.             "A kid will eat ivy too"  
  16.         };  
  17.         Random random = new Random();  
  18.   
  19.         for (int i = 0;  
  20.              i < importantInfo.length;  
  21.              i++) {  
  22.             drop.put(importantInfo[i]);  
  23.             try {  
  24.                 Thread.sleep(random.nextInt(5000));  
  25.             } catch (InterruptedException e) {}  
  26.         }  
  27.         drop.put("DONE");  
  28.     }  
  29. }  


Consumer 是消費者線程,讀取消息並打印出來,直到讀取到字符串DONE爲止。消費者線程在消息讀取時也會隨機的暫停。 

Java代碼 
  1. import java.util.Random;  
  2.   
  3. public class Consumer implements Runnable {  
  4.     private Drop drop;  
  5.   
  6.     public Consumer(Drop drop) {  
  7.         this.drop = drop;  
  8.     }  
  9.   
  10.     public void run() {  
  11.         Random random = new Random();  
  12.         for (String message = drop.take();  
  13.              ! message.equals("DONE");  
  14.              message = drop.take()) {  
  15.             System.out.format("MESSAGE RECEIVED: %s%n", message);  
  16.             try {  
  17.                 Thread.sleep(random.nextInt(5000));  
  18.             } catch (InterruptedException e) {}  
  19.         }  
  20.     }  
  21. }  


ProducerConsumerExample 是主線程,它啓動生產者線程和消費者線程。 

Java代碼 
  1. public class ProducerConsumerExample {  
  2.     public static void main(String[] args) {  
  3.         Drop drop = new Drop();  
  4.         (new Thread(new Producer(drop))).start();  
  5.         (new Thread(new Consumer(drop))).start();  
  6.     }  
  7. }  


注意:Drop類是用來演示Guarded Blocks如何工做的。爲了不從新發明輪子,當你嘗試建立本身的共享數據對象時,請查看 Java Collections Framework 中已有的數據結構。如需更多信息,請參考 Questions and Exercises
相關文章
相關標籤/搜索