8

我知道這個問題以前已經被問過和回答過很多次了,但是我只是無法弄清楚在互聯網上發現的例子,比如thisthat之一。在java中實現你自己的阻塞隊列

這兩種解決方案都檢查阻塞隊列的數組/隊列/鏈表的空態爲notifyAll等待線程在put()方法中,反之亦然get()方法。第二個鏈接中的comment強調了這種情況,並提到這不是必需的。

所以問題是,對我來說,檢查隊列是否爲空也似乎有點奇怪完整地通知所有等待的線程。有任何想法嗎?

在此先感謝。

+2

問題的一部分與在併發程序中正確使用wait和notify有關;引用Joshua Bloch的話說,他們就像「併發編程的低級(彙編語言)」。他主張使用'notifyAll',並且等待的線程在通知時應該總是在循環中進行檢查,以便它們可以根據需要繼續等待。實際上,根本不應該使用wait/notify,而是始終打算使用Java SE 5中提供的更高級別的併發API。而不是使用線程和等待/通知,根據任務和執行器設計併發應用程序。 – scottb

+0

是的,我完全同意你的觀點,老東西但是好吃的東西:不需要重新發明輪子。但只是認爲我們需要使用wait和notifyAll來處理這種情況。我知道,等待()應該被用於循環中,在所有的Javadoc解釋(虛假喚醒):http://docs.oracle.com/javase/6/docs/api/java/lang/Object.html#wait(長)。但是通知的條件是什麼?這是我無法得到的。 – tugcem

+0

參見[這裏](http://fuseyism.com/classpath/doc/java/util/concurrent/LinkedBlockingQueue-source.html) - 他們使用'ReentrantLock'和'Condition's,不'wait' /'notify'在所有。 – OldCurmudgeon

回答

1

我認爲邏輯上在notifyAll()之前做額外檢查沒有任何危害。

一旦您從隊列中放入/獲取某些東西,您就可以簡單地使用notifyAll()。一切仍然有效,而且你的代碼更短。但是,在調用notifyAll()之前,檢查是否有人可能正在等待(通過檢查是否碰到隊列邊界)也沒有任何危害。這個額外的邏輯節省了不必要的notifyAll()調用。

它只是取決於你想要一個更短,更乾淨的代碼,或者你希望你的代碼更有效地運行。 (沒有看過notifyAll()的執行情況,如果沒有人在等待時是便宜的操作,無論如何性能增益可能並不明顯)

0

作者使用notifyAll()的原因是簡單:他們不知道是否有必要,所以他們決定採用「更安全」的選擇。

在上述例子中這將是足夠的只是調用notify()作爲每個單獨的元件加入,只有一個線程等待可以在所有情況下提供服務。

如果您的隊列也可以選擇像addAll(Collection<T> list)這樣在一個步驟中添加多個元素,則會變得更加明顯,因爲在這種情況下,可以準確地提供多個等待空列表的線程:線程作爲元素被添加。

然而,notifyAll()會在特殊的單元素情況下產生額外的開銷,因爲許多線程不必要地被喚醒,因此必須再次進入休眠狀態,同時阻止隊列訪問。所以用notify()代替notifyAll()可以在這種特殊情況下提高速度

但是,然後不是使用等待/通知和所有同步,而是使用併發包會比任何智能等待/通知實現可以獲得更多的速度增加速度。

12

我知道這是迄今爲止的一個老問題,但在閱讀問題和答案之後,我無法幫助自己,我希望你覺得這很有用。

關於檢查如果隊列實際上是滿或空通知其他等待線程之前,你錯過了一些東西,是這兩種方法put (T t)T get()都是​​方法,這意味着只有一個線程可以進入這些方法之一時間,但是這不會阻止他們在一起工作,所以如果一個線程,一個已經進入put (T t)方法的另一個線程-b仍然可以進入並開始執行在T get()方法的指令之前線程一個已經退出put (T t),所以這double-checking設計是否會讓開發人員感覺更安全一些,因爲你無法知道未來的CPU上下文切換是否會發生。

更好和更推薦方法是使用Reentrant LocksConditions

//使用這個方法有沒有必要double checking我編輯的源代碼,從這個link

Condition isFullCondition; 
Condition isEmptyCondition; 
Lock lock; 

public BQueue() { 
    this(Integer.MAX_VALUE); 
} 

public BQueue(int limit) { 
    this.limit = limit; 
    lock = new ReentrantLock(); 
    isFullCondition = lock.newCondition(); 
    isEmptyCondition = lock.newCondition(); 
} 

public void put (T t) { 
    lock.lock(); 
    try { 
     while (isFull()) { 
      try { 
       isFullCondition.await(); 
      } catch (InterruptedException ex) {} 
     } 
     q.add(t); 
     isEmptyCondition.signalAll(); 
    } finally { 
     lock.unlock(); 
    } 
} 

public T get() { 
    T t = null; 
    lock.lock(); 
    try { 
     while (isEmpty()) { 
      try { 
       isEmptyCondition.await(); 
      } catch (InterruptedException ex) {} 
     } 
     t = q.poll(); 
     isFullCondition.signalAll(); 
    } finally { 
     lock.unlock(); 
    } 
    return t; 
} 

,因爲lock對象這兩種方法之間共享,這意味着只有一個線程a或b可以在不同於同步方法時其產生不同的顯示器,並且只有那些線程等待,因爲隊列已滿將被通知的wh輸入任意的這些方法中這裏有更多的空間,對於等待的線程也是如此,因爲隊列是空的,這將導致更好的CPU利用率。 你可以在源代碼中找到更詳細的例子here

+0

代替isfullConditon,「isNotfullCondtion」將使其更具可讀性+1的實施 –