尝试实现Dijkstra算法时出现ConcurrentModificationException

时间:2014-01-24 15:55:41

标签: java hashset concurrentmodification

我正在尝试在迷宫中实现Dijkstra的最短路径算法。 (http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm

我有两个HashSets,一个用于访问,一个用于未访问的字段。 一旦所有邻居通过算法访问了一个字段, 我想把它放在访问过的地图中,并从未访问的地图中删除它。

但是,当我尝试运行算法时,我在Netbeans中得到了一个ConcurrentModificationException。

有趣的是 - 我已经阅读了这个问题,根据我的理解,这个问题来自于试图操纵/删除数据集中的数据,而不是迭代它。但是,我在集合的迭代中得到错误,即:

for( Field unvisited : unvisitedFields ) {

由于我遇到了时间问题,解决这个问题就足够了,但如果我使用不好的做法,我很想知道解决这个问题的更好方法是什么。 下面的方法的完整代码,unvisitedFields被初始化为类变量,但具有与visitedFields相同的参数。它是一个类变量的原因是因为我将它填充在我的类的顶部,以及一个2D数组。

public void calculcateSPath(Field curLocation , Field target ) {

        Set<Field> visitedFields = new HashSet<>();
        ArrayList<Field> shortestPath = new ArrayList<>();

        shortestPath.add( target );
        curLocation .setDistance( 0 );
        unvisitedFields.remove( curLocation  );
        visitedFields.add( curLocation  );

        // until all fields have a corresponding value to field, it continues searching.
        while( unvisitedFields.isEmpty() == false ) {

            // iterate through the Set containing all unvisited fields.
            for( Field unvisited : unvisitedFields ) {

                //iterate through the Set containing all visited fields.
                for( Field posNeighbor : visitedFields ) {

                    // if the unvisited field has a visited field as neighbor
                    if( unvisited.allNeighbors().containsValue( posNeighbor )) {

                        // check if the wall between them is down
                        if(( unvisited.getNeighbor( Direction.up ).equals( posNeighbor ) && posNeighbor.drawDown() == false )
                            || ( unvisited.getNeighbor( Direction.right ).equals( posNeighbor ) && unvisited.drawRight() == false )
                            || ( unvisited.getNeighbor( Direction.down ).equals( posNeighbor ) && unvisited.drawDown() == false )
                            || ( unvisited.getNeighbor( Direction.left ).equals( posNeighbor ) && posNeighbor.drawRight() == false )) {

                            visitedFields.add( posNeighbor );

                            // if so, check if the current distance is smaller than the previous distance.
                            if( unvisited.getDistance() > ( posNeighbor.getDistance()+1 ) ) {

                                // assign the new shorter distance and the connection point
                                unvisited.setDistance( posNeighbor.getDistance() + 1 );
                                unvisited.setVisitedNeighbors( 1 );
                                unvisited.setPrevious( posNeighbor );

                            }
                        // if not, add a count to the visited neighbors    
                        } else {

                            unvisited.setVisitedNeighbors( 1 );

                        }

                        //if all neighbors have been visited, remove the field from the unvisited list and add to visited.
                        if( unvisited.getVisitedNeighbors() == unvisited.allNeighbors().size() ) {

                            unvisitedFields.remove( unvisited );
                            visitedFields.add( posNeighbor );

                        }
                    }
                }
            }
        }

    } // ends calculateSPath()

3 个答案:

答案 0 :(得分:4)

来自API

  

此类的迭代器方法返回的迭代器是快速失败的:   如果在创建迭代器后的任何时间修改了该集,则   除了通过迭代器自己的删除方法,Iterator之外的任何方式   抛出ConcurrentModificationException。因此,面对   并发修改,迭代器快速干净地失败,   而不是冒着任意的,非确定性的行为冒险   未来不确定的时间。

这意味着如果要在迭代时修改集合,则必须使用iterator.remove()方法。不要使用for for each循环,尝试这样的事情:

Collection items = ...
Iterator itr = items.iterator();
while(itr.hasNext()) {
  Object o = itr.next();
  boolean condition = ...
  if(condition) {
    itr.remove();
  }
}

或者,如果您可以(并且希望)在迭代完成后进行修改,您可以执行以下操作:

Collection items = ...
Collection itemsToRemove = ...
for (Object item : items) {
  boolean condition = ...
  if (condition) {
    itemsToRemove.add(item);
  }
}
items.removeAll(itemsToRemove);

如果您的收藏品类型为List,那么您可以通过调用ListIterator获得listIterator()。 ListIterator通过添加允许双向遍历列表的方法来扩充Iterator,并允许通过添加和删除项目或替换当前项目来修改集合。

答案 1 :(得分:3)

在java中使用“for each”循环时,实际上你正在使用Iterator。请参阅Which is more efficient, a for-each loop, or an iterator?

由于您正在修改底层集合(删除)而不使用迭代器的add或remove方法,因此您将获得ConcurrentModificationException,因为Java集合故障快速

  

如果迭代器抛出一个迭代器,则会被视为fail-fast   ConcurrentModificationException下面两个之一   条件:

     
      
  1. 在多线程处理中:如果一个线程正在尝试修改a   当另一个线程迭代它时收集。

  2.   
  3. 在单线程或多线程处理中:如果在创建之后   对于迭代器,容器随时可以通过任何方法进行修改   除了Iterator自己的删除或添加方法。

  4.   

所以你必须明确地使用Iterator,在Iterator而不是集合上调用remove。

答案 2 :(得分:3)

一般提示:在许多情况下,可以通过转换模式来避免ConcurrentModificationException

for (Element element : collection)
{
    if (someCondition) collection.remove(element); // Causes Exception!
}

进入像

这样的模式
Set<Element> toRemove = new HashSet<Element>(); // Or a list
for (Element element : collection)
{
    if (someCondition) toRemove.add(element);
}
collection.removeAll(toRemove);

与明确处理Iterator相比,这通常更方便,更容易理解。它也可能适用于您的情况(尽管,诚然,并没有完全遵循您的代码进入最高嵌套深度)