2013-02-15 61 views
3

我有一個for-each循環在java中。對於每個循環的列表,同時增長

for-each在線程中運行myListmyList可以同時增長。 我有一些問題:

  1. 如果我開始for-each循環,它啓動後,項目被添加到列表中,將在其上的for-each循環運行的操作?
  2. 假設對上述問題的答案是否定的。我有一個問題。 for-each循環處於while(true)循環中,所以它將重新開始。我希望for-each循環在每個項目上運行一次。我無法在for-each循環中刪除項目,因爲我得到ConcurrentModificationException。所以我的解決方案是在for-each循環結束後刪除所有列表項。但是,這樣,如果在for-each循環啓動後添加到列表中的項目也被刪除,則for-each循環將永遠不會在此項目上運行。

我的目標是做一個for-每個運行在可以同時增長的列表上。我希望for-each循環永遠不會錯過一個項目,並且永遠不要在同一個項目上運行兩次或更多次。解決辦法是什麼?

+1

爲什麼你堅持'for-each'循環而不是'for'與索引? – 2013-02-15 12:18:14

回答

6

使用Iterator.remove將允許你不要打ConcurrentModificationException的,但另一種解決方案是不使用foreach循環和簡單循環,是這樣的:

// assuming that this is a list of Strings 
List<String> list = ... 
while(!list.isEmpty())) { 
    String data = list.remove(0); 
    ...process data... 
} 

這將允許你來處理每項目添加到列表中,並只做一次。上面有一個小窗口,其中isEmpty可以返回true,並且可以將新項目添加到列表中(這可能發生在多線程環境中)。

+2

或者在While語句 – 2013-02-15 12:23:01

4

這是一個典型的生產者消費者問題。 你不應該使用列表或列表的任何實現。由於List是基於索引的,因此將項目添加/移除到列表會修改其他元素的索引。

嘗試使用任何Queue實現。

在你的情況下,其他線程(生產者)會排隊到隊列中,並且運行foreach塊的代碼/線程(消費者)塊應該從隊列中出隊並進行處理。

讓我知道這是否有用。如果我對您的使用案例的理解錯誤,請澄清。

-

維諾德

+0

中使用像LinkedBlockingQueue和poll這樣的併發集合,這是很好的解決方法,謝謝。 – Shelef 2013-02-15 12:36:05

0

我認爲你正在尋找一種列表的雙緩衝的。

我已經測試過這與多個生產者和多個消費者,它似乎很好地工作。

基本上,你需要保存一個列表,當被請求時,被一個新的空列表所取代。在交換時正確處理線程會增加一點複雜性。這可以處理添加到列表中的多個線程以及多個線程獲取迭代列表。

請注意,您的架構略有變化(從列表中一次拉一個條目)意味着您可以使用BlockingQueue這可能是一個更好的解決方案。

public class DoubleBufferedList<T> { 
    // Atomic reference so I can atomically swap it through. 
    // Mark = true means I am adding to it so unavailable for iteration. 
    private AtomicMarkableReference<List<T>> list = new AtomicMarkableReference<List<T>>(newList(), false); 

    // Factory method to create a new list - may be best to abstract this. 
    protected List<T> newList() { 
    return new ArrayList<T>(); 
    } 

    // Get and replace the current list. 
    public List<T> getList() { 
    // Atomically grab and replace the list with an empty one. 
    List<T> empty = newList(); 
    List<T> it; 
    // Replace an unmarked list with an empty one. 
    if (!list.compareAndSet(it = list.getReference(), empty, false, false)) { 
     // Failed to replace! 
     // It is probably marked as being appended to but may have been replaced by another thread. 
     // Return empty and come back again soon. 
     return Collections.EMPTY_LIST; 
    } 
    // Successfull replaced an unmarked list with an empty list! 
    return it; 
    } 

    // Add an entry to the list. 
    public void addToList(T entry) { 
    List<T> it; 
    // Spin on get and mark. 
    while (!list.compareAndSet(it = list.getReference(), it, false, true)) { 
     // Spin on mark. 
    } 
    // Successfully marked! Add my new entry. 
    it.add(entry); 
    // Unmark it. Should never fail because once marked it will not be replaced. 
    if (!list.attemptMark(it, false)) { 
     throw new IllegalMonitorStateException("it changed while we were adding to it!"); 
    } 
    } 
}