我在java中有一个for-each循环。
for-each在线程中的myList
上运行。 myList
可以同时增长。
我有一些问题:
ConcurrentModificationException
。所以我的解决方案是在for-each循环结束后删除所有列表项。但是,这样一来,如果在for-each循环开始之后添加到列表中的项目也被删除了,那么for-each循环将永远不会在这个项目上运行。我的目标是在可以同时增长的列表上运行for-each。我希望for-each循环永远不会错过任何项目,并且永远不会在同一项目上运行两次或更多次。解决方案是什么?
答案 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!");
}
}
}