2012-04-26 94 views
5

我想在Java ME中實現一個簡單的阻塞隊列。在JavaME API中,Java SE的併發實用程序不可用,所以我必須像以前一樣使用wait-notify。在JavaME中實現阻塞隊列:如何優化它?

這是我的臨時實施。我使用的是notify而不是notifyAll,因爲在我的項目中有多個生產者,但只有一個消費者。我用了一個對象的宗旨,爲等待通知,以提高可讀性,儘管它浪費一個參考:

import java.util.Vector; 

    public class BlockingQueue {  
     private Vector queue = new Vector(); 
     private Object queueLock = new Object();  

     public void put(Object o){ 
      synchronized(queueLock){ 
       queue.addElement(o); 
       queueLock.notify(); 
      }  
     } 

     public Object take(){ 
      Object ret = null; 
      synchronized (queueLock) { 
       while (queue.isEmpty()){ 
        try { 
         queueLock.wait(); 
        } catch (InterruptedException e) {} 
       } 

       ret = queue.elementAt(0); 
       queue.removeElementAt(0); 
      } 
      return ret; 
     } 
    } 

我的主要問題是關於put方法。我可以將queue.addElement行從​​塊中刪除嗎?如果是,性能會提高嗎?

此外,同樣適用於take:我可以在synchronized block以外的queue兩個操作?

任何其他可能的優化?

編輯:
作爲@Raam正確地指出,消費者線程可以在wait被吵醒時餓死。那麼有什麼辦法可以防止呢? (注意:在JavaME中,我沒有Java SE提供的所有這些很好的類,可以將其視爲舊的Java v1.2)

+1

你可以嘗試backport併發庫,而不是(http://backport-jsr166.sourceforge.net/)? – artbristol 2012-04-26 08:52:41

+1

謝謝,但它說在Java 1.2上沒有很好的測試。另外我不需要這麼多的類,只是一個簡單的隊列。 – 2012-04-26 09:04:31

+0

沒有冒犯,但它仍然可能比自己動手更好的測試。另外,不要這樣做catch(InterruptedException e){}';請參閱http://www.ibm.com/developerworks/java/library/j-jtp05236/index.html – artbristol 2012-04-26 09:42:00

回答

1

Vector類不保證線程安全,並且應該將訪問它,就像你所做的一樣。除非您有證據表明您當前的解決方案存在性能問題,否則我不會擔心。

請注意,我認爲使用notifyAll而不是notify來支持多個消費者沒有任何壞處。

1

​​用於保護對共享狀態的訪問並確保原子性。

請注意,Vector的方法已經同步,因此Vector保護它自己的共享狀態本身。所以,您的同步塊只需要確保您的操作的原子性。

您肯定無法從take()方法的同步塊中移動queue上的操作,因爲原子性對於該方法的正確性至關重要。但是,據我所知,你可以從put()方法中的同步塊中移動隊列操作(我無法想象它可能出錯的情況)。

然而,上述推理是純理論的,因爲在所有情況下,你有雙重同步:您同步上queueLockVector方法上queue隱含同步。 因此提出的優化沒有意義,其正確性取決於雙同步的存在。

爲了避免重複同步需要在queue以及同步:

synchronized (queue) { ... } 

另一種選擇是使用非同步集合(如ArrayList),而不是Vector,但的JavaME不支持。在這種情況下,您將無法使用建議的優化,因爲同步塊也保護非同步集合的共享狀態。

+0

-1 ArrayList在[J2ME/CLDC]中不可用(http://docs.oracle.com/javame/ config/cldc/ref-impl/midp2.0/jsr118/java/util/package-frame.html「用於J2ME java.util的API文檔 - 與Java SE差別很大」)。除此之外,你的推理看起來不錯 – gnat 2012-04-26 09:21:27

+1

@gant:更新。 – axtavt 2012-04-26 09:28:21

+0

Vector的方法是否保證同步?我知道這是Java SE的情況,但MIDP 2.0 Vector的參考實現(http://docs.oracle.com/javame/config/cldc/ref-impl/midp2.0/jsr118/java/util/Vector。 html)沒有提及與線程或同步相關的任何內容。 – claesv 2012-04-26 09:39:06

-2

這種方法似乎存在一些問題。您可以在場景中使用者可以錯過通知並在隊列中等待,即使隊列中有元素。 按時間順序考慮以下順序:

T1 - 消費者獲取隊列鎖,然後調用wait。等待將解除鎖定,導致線程等待通知

T2 - 一個生產者獲取queueLock並添加元素到隊列中,調用notify

T3 - 消費者線程被通知,並嘗試獲取隊列鎖但是失敗,因爲另一個生產者在同一時間。 (來自通知java文檔 - 被喚醒的線程將以通常的方式與其他可能正在主動競爭的線程競爭以在該對象上同步;例如,被喚醒的線程在被下一個線程鎖定時沒有可靠的特權或缺點此對象。)

T4 - 第二個生產者現在添加了另一個元素並調用notify。此消息在消費者在queueLock上等待時丟失。

因此,理論上它可能讓消費者捱餓(永久堅持嘗試獲取queueLock),您也可能遇到內存問題,多個生產者將元素添加到隊列中,而這些元素不會從隊列中讀取或刪除。

了一些變化,我建議如下: -

  • 保持一個上限,可以添加到隊列中的項目數。
  • 確保消費者始終閱讀所有元素。 Here是一個程序,它顯示了生產者 - 消費者問題如何編碼。
+1

在T4中,第二個生產者添加元素,調用notify和釋放鎖。然後,消費者(被通知後被阻止)再次獲得'queueLock',它恢復並繼續。我看到這裏沒有問題。 – 2012-04-26 10:14:53

+0

消費者理論上可以在這一點上捱餓嗎?如果生產者持續流入,它會永遠等待鎖定。 – Raam 2012-04-26 10:18:35

+0

我從昨天開始一直在想這件事,現在我看到你在這裏有一個觀點。但我無法想象有什麼替代方案可以阻止它(我不會接受調整優先級作爲解決方案)。 – 2012-04-27 07:34:22

0

除非你有特別是由於垃圾收集的性能問題,我想還是用鏈表比一個Vector實現隊列(先入先出)。

我還會編寫代碼,當您的項目(或其他)獲取多個使用者時,它們將被重用。儘管在這種情況下,您需要知道Java語言規範並未強制實施監視器。實際上,這意味着你不控制哪個消費線程得到通知(一半的現有Java虛擬機使用FIFO模型實現監視器,另一半實現使用LIFO模型的監視器)

我也認爲無論誰正在使用的阻塞類也應該處理InterruptedException。畢竟,客戶端代碼將不得不處理空的Object返回,否則。

所以,這樣的事情:


/*package*/ class LinkedObject { 

    private Object iCurrentObject = null; 
    private LinkedObject iNextLinkedObject = null; 

    LinkedObject(Object aNewObject, LinkedObject aNextLinkedObject) { 
     iCurrentObject = aNewObject; 
     iNextLinkedObject = aNextLinkedObject; 
    } 

    Object getCurrentObject() { 
     return iCurrentObject; 
    } 

    LinkedObject getNextLinkedObject() { 
     return iNextLinkedObject; 
    } 
} 

public class BlockingQueue { 
    private LinkedObject iLinkedListContainer = null; 
    private Object iQueueLock = new Object(); 
    private int iBlockedThreadCount = 0; 

    public void appendObject(Object aNewObject) { 
     synchronized(iQueueLock) { 
      iLinkedListContainer = new iLinkedListContainer(aNewObject, iLinkedListContainer); 
      if(iBlockedThreadCount > 0) { 
       iQueueLock.notify();//one at a time because we only appended one object 
      } 
     } //synchonized(iQueueLock) 
    } 

    public Object getFirstObject() throws InterruptedException { 
     Object result = null; 
     synchronized(iQueueLock) { 
      if(null == iLinkedListContainer) { 
       ++iBlockedThreadCount; 
       try { 
        iQueueLock.wait(); 
        --iBlockedThreadCount; // instead of having a "finally" statement 
       } catch (InterruptedException iex) { 
        --iBlockedThreadCount; 
        throw iex; 
       } 
      } 
      result = iLinkedListcontainer.getCurrentObject(); 
      iLinkedListContainer = iLinkedListContainer.getNextLinkedObject(); 
      if((iBlockedThreadCount > 0) && (null != iLinkedListContainer)) { 
       iQueueLock.notify(); 
      } 
     }//synchronized(iQueueLock) 
     return result; 
    } 

} 

我認爲,如果你試圖把更少的代碼在synchronized塊,類將不再正確。

+0

不錯的一個。但是如果消費者線程中斷,會發生什麼?它會終止嗎?線程是否應該在其'run'方法中處理'InterruptedException'? – 2012-04-27 07:15:55

+0

這絕對是一種處理中斷的方式,因爲它可能是由於MIDlet被其中一個線程阻塞而被破壞而導致的。 – 2012-04-27 10:13:50