我需要根据ID更新PriorityQueue中的一些固定优先级元素。我认为这是一种非常常见的情况,这是一个示例代码段(Android 2.2):
for (Entry e : mEntries) {
if (e.getId().equals(someId)) {
e.setData(newData);
}
}
然后我使Entry“不可变”(没有setter方法),以便创建一个新的Entry实例并由setData()返回。我将我的方法修改为:
for (Entry e : mEntries) {
if (e.getId().equals(someId)) {
Entry newEntry = e.setData(newData);
mEntries.remove(e);
mEntries.add(newEntry);
}
}
代码似乎工作正常,但有人指出在迭代时修改队列是一个坏主意:它可能抛出一个ConcurrentModificationException,我需要将我想要删除的元素添加到ArrayList并删除它以后。他没有解释原因,对我来说这看起来很费劲,但我在互联网上找不到任何具体的解释。
(This post类似,但优先级可以改变,这不是我的情况)
任何人都可以澄清我的代码有什么问题,我应该如何更改它 - 最重要的是 - 为什么?
谢谢, Rippel
PS:一些实施细节......
PriorityQueue<Entry> mEntries = new PriorityQueue<Entry>(1, Entry.EntryComparator());
使用:
public static class EntryComparator implements Comparator<Entry> {
public int compare(Entry my, Entry their) {
if (my.mPriority < their.mPriority) {
return 1;
}
else if (my.mPriority > their.mPriority) {
return -1;
}
return 0;
}
}
答案 0 :(得分:6)
此代码位于PriorityQueue的Java 6实现中:
private class Itr implements Iterator<E> {
/**
* The modCount value that the iterator believes that the backing
* Queue should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
private int expectedModCount = modCount;
public E next() {
if(expectedModCount != modCount) {
throw new ConcurrentModificationException();
}
}
}
现在,为什么这个代码在这里?如果查看Javadoc for ConcurrentModificationException,如果在迭代完成之前对底层集合进行修改,您将发现迭代器的行为是未定义的。因此,许多集合实现了这种modCount
机制。
修复您的代码
您需要确保不要在循环中修改代码。如果您的代码是单线程的(如图所示),那么您可以按照同事的建议进行操作,然后将其复制到列表中以供日后使用。此外,记录了Iterator.remove()方法的使用以防止ConcurrentModificationExceptions。一个例子:
List<Entry> toAdd = new ArrayList<Entry>();
Iterator it = mEntries.iterator();
while(it.hasNext()) {
Entry e = it.next();
if(e.getId().equals(someId)) {
Entry newEntry = e.setData(newData);
it.remove();
toAdd.add(newEntry);
}
}
mEntries.addAll(toAdd);
答案 1 :(得分:0)
稍微好一点的实现
List<Entry> toAdd = new ArrayList<Entry>();
for (Iterator<Entry> it= mEntries.iterator();it.hasNext();) {
Entry e = it.next();
if (e.getId().equals(someId)) {
Entry newEntry = e.setData(newData);
it.remove();
toAdd.add(newEntry);
}
}
mEntries.addAll(toAdd);
这使用了删除迭代器和后续的批量添加
答案 2 :(得分:0)
PriorityQueue的Javadoc明确地说:
“请注意,此实现未同步。如果任何线程在结构上修改列表,则多个线程不应同时访问PriorityQueue实例。而是使用线程安全的 PriorityBlockingQueue 类。” / p>
这似乎是你的情况。
答案 3 :(得分:0)
您的代码中已经解释了什么 - 实现迭代器,它可以通过交叉修改一致地迭代集合是相当困难的任务。你需要指定如何处理被删除的项目(将通过迭代器看到吗?),添加的项目,修改的项目......即使你可以一致地执行它,它将是相当复杂和无效的实现 - 并且,大多数情况下,不是非常有用,因为用例“无需修改迭代”就更常见了。因此,java架构师选择在迭代时拒绝修改,并且Java collection API中的大多数集合都遵循这一点,如果检测到这样的修改,则抛出ConcurrentModificationException。
至于你的代码 - 对我来说,你不应该让项目不可变。不变性是件好事,但不应过度使用。如果您在这里使用的Entry对象是某种域对象,并且您确实希望它们是不可变的 - 您可以创建某种临时数据持有者(MutableEntry)对象,在算法中使用它,并将数据复制到Entry之前返回。从我的角度来看,这将是最好的解决方案。