迭代时更新 PriorityQueue

Posted

技术标签:

【中文标题】迭代时更新 PriorityQueue【英文标题】:Updating a PriorityQueue when iterating it 【发布时间】:2011-08-18 10:25:24 【问题描述】:

我需要根据它们的 ID 更新 PriorityQueue 中的一些固定优先级元素。我认为这是一个很常见的场景,这里有一个示例 sn-p (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 类似,但优先级可以改变,这不是我的情况)

谁能澄清我的代码有什么问题,我应该如何更改它以及 - 最重要的是 - 为什么?

谢谢, 涟漪


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;
    

【问题讨论】:

可能创建一个堆栈,然后在完成后将所需的元素添加到那里。 【参考方案1】:

这段代码在 Java 6 的 PriorityQueue 实现中:

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);

【讨论】:

很好的解释和解决方案:)【参考方案2】:

PriorityQueue 的 Javadoc 明确指出:

“请注意,此实现不是同步的。如果任何线程在结构上修改列表,则多个线程不应同时访问 PriorityQueue 实例。相反,请使用线程安全的 PriorityBlockingQueue 类。”

这似乎是你的情况。

【讨论】:

【参考方案3】:

您的代码中的问题已经解释过了——实现迭代器,它可以通过交叉修改一致地迭代集合,这是一项相当艰巨的任务。您需要指定如何处理已删除的项目(将通过迭代器看到吗?),添加的项目,修改的项目......即使您可以始终如一地做到这一点,它也将是相当复杂和低效的实现——而且,大多数情况下,不是非常有用,因为用例“无需修改即可迭代”更为常见。因此,Java 架构师选择在迭代时拒绝修改,Java 集合 API 中的大多数集合都遵循这一点,如果检测到此类修改,则抛出 ConcurrentModificationException。

至于你的代码——对我来说,你不应该让项目不可变。不变性是件好事,但不应过度使用。如果您在此处使用的 Entry 对象是某种领域对象,并且您真的希望它们是不可变的——您可以创建某种临时数据持有者 (MutableEntry) 对象,在您的算法中使用它,然后将数据复制到 Entry返回。从我的角度来看,这将是最好的解决方案。

【讨论】:

【参考方案4】:

一个稍微好一点的实现是

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);

这使用了迭代器的删除和之后的批量添加

【讨论】:

以上是关于迭代时更新 PriorityQueue的主要内容,如果未能解决你的问题,请参考以下文章

QCustomPlot版本迭代日志

在python中迭代和更新列表[重复]

PyQt5如何使用python在迭代循环中更新单元格值Qtablewidget

更新 SQLite 数据库 Android RecyclerView 迭代

如何在 Java 中迭代 HashMap 值时替换它们

迭代 ConcurrentHashMap 值线程安全吗?