轉:【Java並發編程】之十二:線程間通訊中notifyAll造成的早期通知問題(含代碼)

來源:互聯網
上載者:User

標籤:com   exce   star   ted   strong   targe   現象   article   static   

轉載請註明出處:http://blog.csdn.net/ns_code/article/details/17229601

 

    如果線程在等待時接到通知,但線程等待的條件還不滿足,此時,線程接到的就是早期通知,如果條件滿足的時間很短,但很快又改變了,而變得不再滿足,這時也將發生早期通知。這種現象聽起來很奇怪,下面通過一個樣本程式來說明問題。

    很簡單,兩個線程等待刪除List中的元素,同時另外一個線程正要向其中添加項目。代碼如下:

[java] view plain copy
  1. import java.util.*;  
  2.   
  3. public class EarlyNotify extends Object {  
  4.     private List list;  
  5.   
  6.     public EarlyNotify() {  
  7.         list = Collections.synchronizedList(new LinkedList());  
  8.     }  
  9.   
  10.     public String removeItem() throws InterruptedException {  
  11.         print("in removeItem() - entering");  
  12.   
  13.         synchronized ( list ) {  
  14.             if ( list.isEmpty() ) {  //這裡用if語句會發生危險  
  15.                 print("in removeItem() - about to wait()");  
  16.                 list.wait();  
  17.                 print("in removeItem() - done with wait()");  
  18.             }  
  19.   
  20.             //刪除元素  
  21.             String item = (String) list.remove(0);  
  22.   
  23.             print("in removeItem() - leaving");  
  24.             return item;  
  25.         }  
  26.     }  
  27.   
  28.     public void addItem(String item) {  
  29.         print("in addItem() - entering");  
  30.         synchronized ( list ) {  
  31.             //添加元素  
  32.             list.add(item);  
  33.             print("in addItem() - just added: ‘" + item + "‘");  
  34.   
  35.             //添加後,通知所有線程  
  36.             list.notifyAll();  
  37.             print("in addItem() - just notified");  
  38.         }  
  39.         print("in addItem() - leaving");  
  40.     }  
  41.   
  42.     private static void print(String msg) {  
  43.         String name = Thread.currentThread().getName();  
  44.         System.out.println(name + ": " + msg);  
  45.     }  
  46.   
  47.     public static void main(String[] args) {  
  48.         final EarlyNotify en = new EarlyNotify();  
  49.   
  50.         Runnable runA = new Runnable() {  
  51.                 public void run() {  
  52.                     try {  
  53.                         String item = en.removeItem();  
  54.                         print("in run() - returned: ‘" +   
  55.                                 item + "‘");  
  56.                     } catch ( InterruptedException ix ) {  
  57.                         print("interrupted!");  
  58.                     } catch ( Exception x ) {  
  59.                         print("threw an Exception!!!\n" + x);  
  60.                     }  
  61.                 }  
  62.             };  
  63.   
  64.         Runnable runB = new Runnable() {  
  65.                 public void run() {  
  66.                     en.addItem("Hello!");  
  67.                 }  
  68.             };  
  69.   
  70.         try {  
  71.             //啟動第一個刪除元素的線程  
  72.             Thread threadA1 = new Thread(runA, "threadA1");  
  73.             threadA1.start();  
  74.   
  75.             Thread.sleep(500);  
  76.       
  77.             //啟動第二個刪除元素的線程  
  78.             Thread threadA2 = new Thread(runA, "threadA2");  
  79.             threadA2.start();  
  80.   
  81.             Thread.sleep(500);  
  82.             //啟動增加元素的線程  
  83.             Thread threadB = new Thread(runB, "threadB");  
  84.             threadB.start();  
  85.   
  86.             Thread.sleep(10000); // wait 10 seconds  
  87.   
  88.             threadA1.interrupt();  
  89.             threadA2.interrupt();  
  90.         } catch ( InterruptedException x ) {}  
  91.     }  
  92. }  

    執行結果如下:

    

     分析:首先啟動threadA1,threadA1在removeItem()中調用wait(),從而釋放list上的對象鎖。再過500ms,啟動threadA2,threadA2調用removeItem(),擷取list上的對象鎖,也發現列表為空白,從而在wait()方法處阻塞,釋放list上的對象鎖。再過500ms後,啟動threadB,並調用addItem,獲得list上的對象鎖,並在list中添加一個元素,同時用notifyAll通知所有線程。

    threadA1和threadA2都從wait()返回,等待擷取list對象上的對象鎖,並試圖從列表中刪除添加的元素,這就會產生麻煩,只有其中一個操作能成功。假設threadA1擷取了list上的對象鎖,並刪除元素成功,在退出synchronized代碼塊時,它便會釋放list上的對象鎖,此時threadA2便會擷取list上的對象鎖,會繼續刪除list中的元素,但是list已經為空白了,這便會拋出IndexOutOfBoundsException。

 

    要避免以上問題只需將wait外圍的if語句改為while迴圈即可,這樣當list為空白時,線程便會繼續等待,而不會繼續去執行刪除list中元素的代碼。

    修改後的執行結果如下:

 

     總結:在使用線程的等待/通知機制時,一般都要在while迴圈中調用wait()方法,滿足條件時,才讓while迴圈退出,這樣一般也要配合使用一個boolean變數(或其他能判斷真假的條件,如本文中的list.isEmpty()),滿足while迴圈的條件時,進入while迴圈,執行wait()方法,不滿足while迴圈的條件時,跳出迴圈,執行後面的代碼。

轉:【Java並發編程】之十二:線程間通訊中notifyAll造成的早期通知問題(含代碼)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.