Почему метод map.remove сообщает об ошибке при использовании обхода хэш-карты?

Java

проблема найдена

Когда я недавно отлаживал ошибку проекта, автор столкнулся с очень странной ошибкой, то есть при обходе коллекции хэш-карт одновременно выполнялась операция удаления, и эта операция в итоге привела к ошибке java.util.ConcurrentModificationException брошенный.
При сомнениях обратитесь к приведенному ниже исходному коду, чтобы проанализировать причину проблемы.
Сначала воспроизведите задачу, постройте карту и добавьте к ней элементы:

private static HashMap<Integer, String> map = new HashMap<Integer, String>();;
	public static void main(String[] args) {
  	        for(int i = 0; i < 10; i++){  
	            map.put(i, "value" + i);  
	        }  
	}

Затем удалите некоторые элементы, после чего будет сообщено об ошибке java.util.ConcurrentModificationException.

    for(Map.Entry<Integer, String> entry : map.entrySet()){  
         Integer key = entry.getKey();  
         if(key % 2 == 0){  
             System.out.println("To delete key " + key);  
             map.remove(key);  
             System.out.println("The key " + + key + " was deleted");  
         }  

报错

анализировать проблему

Из отчета об ошибке видно, что в методе HashMap$HashIterator.nextNode есть ошибка в коде.

Давайте посмотрим, что делает операция удаления хэш-карты:

Здесь modCount самоувеличивается, указывая, что действие операции равно +1. Посмотрите, что такое modCount и ожидаемыйModCount.

проблема вызывает

Видно, что modCount и ожидаемыйModCount синхронизируются при инициализации итератора.
На этом этапе вы можете увидеть причину ошибки:

  • Переменная modCount поддерживается в хэш-карте, а переменная expectModCount поддерживается в итераторе Сначала они одинаковы.
  • Каждый раз, когда выполняется операция hashmap.remove, добавляется modCount+1, а ожидаемое значение ModCount в итераторе остается прежним.
  • При следующем вызове next() на итераторе проверьте, является ли HashMap.this.modCount != this.expectedModCount, и если это так, выдайте исключение.

Решать проблему

При каких обстоятельствах элементы карты могут быть удалены во время обхода? Взгляните на метод удаления, предоставляемый итератором:

Видно, что после удаления элемента в итераторе ожидаемыйModCount будет переназначен, так что при повторном обходе не будет сообщено об ошибке. Таким образом, предыдущий код можно изменить на следующий способ прямого вызова метода удаления итератора.

 Iterator<Map.Entry<Integer, String>> it = map.entrySet().iterator();
      while(it.hasNext()){
          Map.Entry<Integer, String> entry = it.next();
          Integer key = entry.getKey();
          if(key % 2 == 0){
         	 System.out.println("To delete key " + key);
         	 it.remove();    
         	 System.out.println("The key " + + key + " was deleted");

          }
      }

Суммировать

  • По сути, классы коллекций Java (включая список и карту) будут сообщать об ошибках ConcurrentModificationException, когда они будут пройдены без использования итераторов для их удаления.Это механизм быстрого отказа, и первоначальное намерение состояло в том, чтобы обнаруживать ошибки.
  • С точки зрения непрофессионала, этот механизм предназначен для предотвращения несогласованности данных, вызванной одновременным изменением элементов карты или списка несколькими потоками в случае высокого параллелизма.Это означает, что другие потоки изменили коллекцию до тех пор, пока текущий modCount != ожидаемый ModCount оценивается. .

Замена механизма:

  • Используйте метод удаления итератора.
  • Замените HashMap на currentHashMap.