2017-02-09 28 views
1

我正在做一個示例程序wait()notify(),但是當調用notify()時,多個線程被喚醒而不是一個。通知似乎在喚醒多個線程

的代碼是:

public class MyQueue<T> { 

    Object[] entryArr; 
    private volatile int addIndex; 

    private volatile int pending = -1; 
    private final Object lock = new Object(); 

    private volatile long notifiedThreadId; 
    private int capacity; 

    public MyQueue(int capacity) { 
     entryArr = new Object[capacity]; 
     this.capacity = capacity; 
    } 

    public void add(T t) { 
     synchronized (lock) { 
      if (pending >= 0) { 
       try { 
        pending++; 
        lock.wait(); 
        System.out.println(notifiedThreadId + ":" + Thread.currentThread().getId()); 
       } catch (InterruptedException e) { 
        e.printStackTrace(); 
       } 
      } else if (pending == -1) { 
       pending++; 
      } 
     } 

     if (addIndex == capacity) { // its ok to replace existing value 
      addIndex = 0; 
     } 

     try { 
      entryArr[addIndex] = t; 
     } catch (ArrayIndexOutOfBoundsException e) { 
      System.out.println("ARRAYException:" + Thread.currentThread().getId() + ":" + pending + ":" + addIndex); 
      e.printStackTrace(); 
     } 

     addIndex++; 

     synchronized (lock) { 
      if (pending > 0) { 
       pending--; 
       notifiedThreadId = Thread.currentThread().getId(); 
       lock.notify(); 
      } else if (pending == 0) { 
       pending--; 
      } 
     } 
    } 

} 

public class TestMyQueue { 

    public static void main(String args[]) { 
     final MyQueue<String> queue = new MyQueue<>(2); 

     for (int i = 0; i < 200; i++) { 
      Runnable r = new Runnable() { 
       @Override 
       public void run() { 
        for (int i = 0; i < Integer.MAX_VALUE; i++) { 
         queue.add(Thread.currentThread().getName() + ":" + i); 
        } 
       } 
      }; 
      Thread t = new Thread(r); 
      t.start(); 
     } 
    } 

} 

一段時間後,我看到兩個螺紋爲喚醒由單個線程。輸出如下:

91:114 
114:124 
124:198 
198:106 
106:202 
202:121 
121:40 
40:42 
42:83 
83:81 
81:17 
17:189 
189:73 
73:66 
66:95 
95:199 
199:68 
68:201 
201:70 
70:110 
110:204 
204:171 
171:87 
87:64 
64:205 
205:115 

這裏我看到115個線程通知了兩個線程,而84個線程通知了兩個線程;因爲這個,我們看到了ArrayIndexOutOfBoundsException

115:84 

115:111 

84:203 

84:200 

ARRAYException:200:199:3 

ARRAYException:203:199:3 

程序中出現什麼問題?

+2

看來你錯過了'synchronized'的實際目的。這是爲了保護對共享資源的訪問,而不是爲了在完全不受保護的情況下訪問共享資源時執行等待和通知。此外,您應該仔細閱讀['Object.wait()'](https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait--)的文檔,尤其是「* ...虛假喚醒是可能的,並且這種方法應該總是用於循環*」部分。 – Holger

+0

感謝您的快速回復。我知道我們可以使用併發Lock。但我的任務是使用wait()和notify()進行鎖定。所以在任何時候,只有一個線程應該執行同步塊之間的代碼。 – CodingJDev

+0

在我的評論中,我說過什麼關於「併發鎖定」?'synchronized'塊*必須跨越整個操作*,包括對共享數據結構的每次訪問,而不僅僅是執行「等待」或「通知」的部分。你正在''synchronized''塊之外訪問'entryArr'和'addIndex',並且聲明'addIndex'爲'volatile'並不會幫助,因爲它不會使更新成爲原子。 – Holger

回答

1

程序中有什麼問題?

您的代碼有幾個問題可能導致此行爲。首先,@Holder評論說,有很多代碼段可以同時由多個線程運行,應該使用​​塊來保護。

例如:

if (addIndex == capacity) { 
    addIndex = 0; 
} 

如果多個線程運行此則多線程可能會看到addIndex == capacity和多會覆蓋第0指數。另一個例子是:

addIndex++; 

這是一個經典的競爭條件,如果2個線程試圖同時執行這個語句。如果addIndex事先爲0,則在2個線程執行此語句後,addIndex的值可能爲1或2,具體取決於競爭條件。

任何可能由多個線程同時執行的語句都必須在​​塊中正確鎖定或以其他方式保護。即使您有volatile字段,仍然可能存在爭用條件,因爲正在執行多個操作。

另外,一個典型的錯誤是在檢查數組上的流量過多或過少時使用if語句。他們應該是while聲明以確保您沒有階級消費者生產者的競爭條件。看到my docs here或看看相關的SO問題:Why does java.util.concurrent.ArrayBlockingQueue use 'while' loops instead of 'if' around calls to await()?