对于同时增长的列表中的每个循环

时间:2013-02-15 12:04:25

标签: java multithreading list arraylist

我在java中有一个for-each循环。

for-each在线程中的myList上运行。 myList可以同时增长。 我有一些问题:

  1. 如果我启动了for-each循环并且在启动之后,一个项目被添加到列表中,那么for-each循环中的动作是否会在它上面运行?
  2. 假设上述问题的答案是否定的。我有点问题。 for-each循环处于while(true)循环中,因此它将重新开始。我希望for-each循环在每个项目上运行一次。我无法在for-each循环中删除项目,因为我得到了ConcurrentModificationException。所以我的解决方案是在for-each循环结束后删除所有列表项。但是,这样一来,如果在for-each循环开始之后添加到列表中的项目也被删除了,那么for-each循环将永远不会在这个项目上运行。
  3. 我的目标是在可以同时增长的列表上运行for-each。我希望for-each循环永远不会错过任何项目,并且永远不会在同一项目上运行两次或更多次。解决方案是什么?

3 个答案:

答案 0 :(得分: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,并且可以将新项添加到列表中(这可能发生在多线程环境中)。

答案 1 :(得分:4)

这是典型的生产者消费者问题。 您不应该使用List或列表的任何实现。由于List是基于索引的添加/删除列表中的项目,因此修改其他元素的索引。

尝试使用任何Queue实现。

在你的情况下,其他线程(生产者)会排队到队列中,运行foreach块的代码/线程(消费者)块应该从队列中出列并进行处理。

如果这符合目的,请告诉我。如果我对您的用例的理解是错误的,请澄清。

-

维诺德

答案 2 :(得分: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!");
    }
  }
}