Статьи

Как бороться с ConcurrentModificationException в Java?

Одной из распространенных проблем при удалении элементов из ArrayList в Java является исключение ConcurrentModificationException. Если вы используете классический цикл for с индексом или расширенный цикл for и пытаетесь удалить элемент из ArrayList с помощью метода remove() , вы получите oncurrentModificationException C oncurrentModificationException но если вы используете метод удаления Iterator или метод ListIterator’s
метод remove() , тогда вы не получите эту ошибку и не сможете удалить элемент. Это неписанное правило в Java, что при циклическом просмотре списка не следует add() элементы add() или remove() пока коллекция не поддерживает отказоустойчивый итератор, например, CopyOnWriteArrayList , которые работают с копией списка, а не с оригинальным списком.

Основная проблема с этой ошибкой состоит в том, что это смущает разработчика, что список изменяется несколькими потоками, и именно поэтому Java выдает эту ошибку, это не так. Большую часть времени
ConcurrentModificationException приходит даже без нескольких потоков, изменяющих список.

Это неправильно, не обманывайтесь этим. хотя кажется естественным думать, что, возможно, какой-то другой поток пытается изменить коллекцию одновременно, он обычно нарушает правило Java.

В этой статье я объясню эту ошибку, и мы приведем множество примеров кода, чтобы воспроизвести этот код даже в одном потоке, и узнаем, как избежать ошибки одновременного изменения при изменении ArrayList в Java.

Кстати, если вы не знакомы с коллекционными классами, например, с самим ArrayList, то вам следует присоединиться к онлайн-курсу, например
Основы Java: научитесь правильно писать код на Udemy — это хорошее место для начала.

ConcurrentModificationException в одной теме

Это первый пример воспроизведения исключения одновременной модификации в Java. В этой программе мы перебираем ArrayList, используя расширенный цикл foreach, и удаляем селективные элементы, например, элемент, который соответствует определенному условию, используя метод удаления ArrayList.

Например, в приведенном ниже коде мы сначала добавили пару хороших книг по программированию, например, « Программирование жемчужин» , « Чистый код» , « Код завершен», в ArrayList, а затем удалили любой элемент, в заголовке которого есть «Код».

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package beginner;
 
import java.util.ArrayList;
import java.util.List;
 
public class HelloWorldApp{
 
   public static void main(String... args){
       List<String> listOfBooks = new ArrayList<>(); 
       listOfBooks.add("Programming Pearls");
       listOfBooks.add("Clean Code");
       listOfBooks.add("Effective Java");
       listOfBooks.add("Code Complete");
        
       // Using forEach loop to iterate and removing
       // element during iteration will throw
       // ConcurrentModificationException in Java
       for(String book: listOfBooks){
           if(book.contains("Code"));{
               listOfBooks.remove(book);
           }
       }
   }
 
}
Output
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
    at java.util.ArrayList$Itr.next(Unknown Source)
    at beginner.HelloWorldApp.main(HelloWorldApp.java:18)

Вы можете видеть, что эта ошибка возникает, хотя у нас только один поток, основной поток, который работает с ArrayList. Ошибка ConcurrentModification происходит потому, что мы не используем Iterator, а просто вызываем listOfBooks.remove() .

В этом коде я использовал Java 1.5, улучшенную для цикла, вы должны знать, как расширен для цикла работает в Java.

Разница между циклом for и расширенным циклом for заключается в том, что позднее внутренне использует Iterator для просмотра всех элементов коллекции. Для более глубокого обсуждения, см. Здесь

Использование Classical для цикла и ArrayList.remove (index)

Вот еще один интересный пример кода удаления элементов из ArrayList. Удивительно, но этот код не вызовет исключение ConcurrentModificationException при первом его запуске? ты знаешь почему?

Что ж, попробуйте, прежде чем посмотреть объяснение после кода. Это действительно мелкие детали о языке программирования Java и платформе Collection, которые сделают вас хорошим разработчиком, а также помогут вам получить сертификат Java, если вы к нему готовитесь.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package beginner;
 
import java.util.ArrayList;
import java.util.List;
 
public class HelloWorldApp{
 
   public static void main(String... args){
       List<String> listOfBooks = new ArrayList<>(); 
       listOfBooks.add("Programming Pearls");
       listOfBooks.add("Clean Code");
       listOfBooks.add("Effective Java");
       listOfBooks.add("Code Complete");
        
       System.out.println("List before : " + listOfBooks);
       for(int i=0; i<listOfBooks.size(); i++){
           String book = listOfBooks.get(i);
           if(book.contains("Programming")){
               System.out.println("Removing " + book);
               listOfBooks.remove(i); // will throw CME
           }
       }
       System.out.println("List after : " + listOfBooks);
   }
 
}
 
Output
List before : [Programming Pearls, Clean Code, Effective Java, Code Complete]
Removing Programming Pearls
List after : [Clean Code, Effective Java, Code Complete]

Этот код не генерирует ConcurrentModificationException потому что здесь мы не используем Iterator, а просто используем традиционный цикл for.

Это итератор, который ConcurrentModificationException исключение ConcurrentModificationException , а не метод удаления ArrayList , поэтому вы не увидите эту ошибку в приведенном ниже коде.

Если вы посмотрите на код для ArrayList.java , вы заметите, что есть вложенный класс, который реализовал интерфейс Iterator, и его метод next () вызывает функцию checkForComodification() которая фактически проверяет, изменился ли ArrayList во время итерации или нет, если modCount не делает этого. не совпадает с expectedModCount то он вызывает ConcurrentModificationException .

1
2
3
4
final void checkForComodification() {
  if (modCount != expectedModCount)
    throw new ConcurrentModificationException();
}

Этот тип вопросов также очень популярен в Oracle Java Certification, например, OCAJP ( 1z0-808 ) и OCPJP ( 1Z0-809 ), поэтому, если вы готовитесь к этим экзаменам, вы должны знать ответ.

Вот полный фрагмент кода из класса ArrayList.java для вашей быстрой ссылки:

Использование Iterator, но метод удаления ArrayList

Теперь давайте посмотрим на другой пример кода, где Java-программист думает, что он все сделал правильно, но все еще получает исключение одновременной модификации. Можете ли вы определить ошибку? Это действительно часто встречается, и я много раз видел такой код на форумах Java, в StackOverflow и в группах Java на Facebook, где они просили решить проблему.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package beginner;
 
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
 
public class HelloWorldApp{
 
   public static void main(String... args){
       List<String> listOfBooks = new ArrayList<>(); 
       listOfBooks.add("Programming Pearls");
       listOfBooks.add("Clean Code");
       listOfBooks.add("Effective Java");
       listOfBooks.add("Code Complete");
        
       Iterator<String> iterator = listOfBooks.iterator();
       while(iterator.hasNext()){
           String book = iterator.next();
           listOfBooks.remove(book);
       }
   }
 
}
 
Output
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
    at java.util.ArrayList$Itr.next(Unknown Source)
    at beginner.HelloWorldApp.main(HelloWorldApp.java:18)

Реальная проблема с этим кодом состоит в том, что, хотя код использует Iterator для ArrayList , он на самом деле не использует метод Iterator.remove() для удаления элемента. Он просто использует Iterator для получения следующего элемента, но вызывает метод ArrayList.remove () для удаления элемента.

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

Кстати, если вы изучаете Java, я предлагаю присоединиться к Complete Java Masterclass, чтобы лучше изучить Java и избежать таких распространенных ошибок.

Правильный способ удаления элемента — использование метода удаления Итератора.

Наконец, вот правильный способ удалить элемент из ArrayList во время итерации. В этом примере мы использовали Iterator как для итерации, так и для удаления элемента. Код в порядке, но имеет серьезные ограничения, вы можете использовать этот код только для удаления текущего элемента. Вы не можете удалить любой произвольный элемент из ArrayList в Java.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package beginner;
 
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
 
public class HelloWorldApp{
 
   public static void main(String... args){
       List<String> listOfBooks = new ArrayList<>(); 
       listOfBooks.add("Programming Pearls");
       listOfBooks.add("Clean Code");
       listOfBooks.add("Effective Java");
       listOfBooks.add("Code Complete");
        
       System.out.println("List before : " + listOfBooks);
       Iterator<String> iterator = listOfBooks.iterator();
       while(iterator.hasNext()){
           String book = iterator.next();
           System.out.println("Removing " + book);
           iterator.remove();
       }
       System.out.println("List after : " + listOfBooks);
   }
 
}
Output
List before : [Programming Pearls, Clean Code, Effective Java, Code Complete]
Removing Programming Pearls
Removing Clean Code
Removing Effective Java
Removing Code Complete
List after : []

Такое же поведение применимо и к ListIterator . Я имею в виду, вы можете заменить Iterator на ListIterator и код будет работать нормально. ListIterator также позволяет вам перемещаться в обоих направлениях, то есть вперед и назад.

Это все о том, как избежать ConcurrentModificationException при удалении элементов из ArrayList во время итерации . Вы можете использовать ту же технику, чтобы избежать исключения ConcurrentModificationException при удалении элементов из любых других классов коллекций, которые имеют итератор без сбоев, например LinkedList. Кстати, если вы новичок в программировании на Java, то присоединение к хорошему, всеобъемлющему курсу, подобному основам Java: научитесь правильно кодировать на Udemy, поможет вам лучше и быстрее освоить Java.

Другие руководства по устранению неполадок Java, которые могут вам понравиться

Как решить ArrayIndexOutOfBoundsException в Java? ( руководство )
Как решить NullPointerException в Java? (руководство)
Как устранить ошибку «Система не может найти указанный путь»? ( решение )
Как решить NoClassDefFoundError при запуске программы Java из командной строки? ( решение )
Как устранить ошибку «Не найдена JVM, установите 64-битный JDK» в Android Studio? ( решение )
Как бороться с ошибкой SQLException «Не найден подходящий драйвер» в JDBC и MySQL? ( руководство )
Как решить NumberFormatException в Java? ( руководство )
Как решить Minecraft — java.lang.UnsatisfiedLinkError: lwjgl64.dll: доступ запрещен? ( решение )
Как исправить java.lang.ArrayIndexOutOfBoundsException: 1 в Java? ( решение )
Как исправить java.net.SocketException: программное обеспечение вызвало прерывание соединения: сбой recv ( исправить )

Спасибо за чтение этого урока. Если вам нравится этот урок, поделитесь им с друзьями и коллегами. Если у вас есть какие-либо вопросы или предложения, пожалуйста, оставьте комментарий.

Посмотрите оригинальную статью здесь: Как бороться с ConcurrentModificationException в Java? Осторожно, удаляя элементы из ArrayList в цикле

Мнения, высказанные участниками Java Code Geeks, являются их собственными.