簡體   English   中英

嘗試實現Dijkstra算法時出現ConcurrentModificationException

[英]ConcurrentModificationException while trying to implement Dijkstra algorithm

我正在嘗試在迷宮中實現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()

來自API

這個類的迭代器方法返回的迭代器是快速失敗的:如果在創建迭代器之后的任何時候修改了set,​​除了通過迭代器自己的remove方法之外,Iterator拋出ConcurrentModificationException。 因此,在並發修改的情況下,迭代器快速而干凈地失敗,而不是在未來的未確定時間冒着任意的,非確定性行為的風險。

這意味着如果要在迭代時修改集合,則必須使用iterator.remove()方法。 不要使用for循環,嘗試這樣的事情:

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,並允許通過添加和刪除項目或替換當前項目來修改集合。

在java中使用“for each”循環時,實際上你正在使用Iterator。 請參閱哪個更有效,for-each循環或迭代器?

由於您在不使用迭代器的add或remove方法的情況下修改底層集合(remove),因此您收到ConcurrentModificationException因為Java集合是快速失敗的

如果迭代器在以下兩個條件之一中拋出ConcurrentModificationException,則認為迭代器是快速失敗的

  1. 在多線程處理中:如果一個線程正在嘗試修改Collection而另一個線程正在迭代它。

  2. 在單線程或多線程處理中:如果在創建Iterator之后,可以通過除Iterator自己的remove或add方法之外的任何方法隨時修改容器。

因此,您必須顯式使用Iterator,在Iterator而不是集合上調用remove。

一般提示:在許多情況下,可以通過轉換模式來避免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相比,這通常更方便,更容易理解。 它也可能適用於您的情況(但誠然,並未完全遵循您的代碼進入最高嵌套深度)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM