Сегодня мы рассмотрим способ избежать ConcurrentModificationException
при использовании Iterator
. Эта ситуация чаще всего возникает в тех случаях, когда с помощью итератора проходят по элементам коллекции и в какой-то момент при вызове iterator.next()
будет брошен ConcurrentModificationException
. Эта ситуация может произойти как в многопоточной, так и в однопоточной среде.
Давайте рассмотрим это на простом примере:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
... public class IteratorExample { public static void main(String args[]){ List<String> myList = new ArrayList<String>(); myList.add("1"); myList.add("2"); myList.add("3"); myList.add("4"); myList.add("5"); Iterator<String> it = myList.iterator(); while(it.hasNext()){ String value = it.next(); System.out.println("List Value:"+value); if(value.equals("3")) myList.remove(value); } Map<String,String> myMap = new HashMap<String,String>(); myMap.put("1", "1"); myMap.put("2", "2"); myMap.put("3", "3"); Iterator<String> it1 = myMap.keySet().iterator(); while(it1.hasNext()){ String key = it1.next(); System.out.println("Map Value:"+myMap.get(key)); if(key.equals("2")){ myMap.put("1","4"); //myMap.put("4", "4"); } } } } |
Результатом выполнения этого кода будет следующее:
1 2 3 4 5 6 7 |
List Value:1 List Value:2 List Value:3 Exception in thread "main" java.util.ConcurrentModificationException at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372) at java.util.AbstractList$Itr.next(AbstractList.java:343) at ua.com.prologistic.java.IteratorExample.main(IteratorExample.java:27) |
Исходя из результатов выполнения, исключение наступает в то время, когда мы называем метод next()
нашего итератора.
Хотите знать как Iterator
проверяет количество изменений в коллекции? Вот небольшая справка:
В классе
AbstractList
есть переменнаяmodCount
, где хранится количество изменений списка. Это значение используется в каждом следующем вызове методаnext()
для проверки каких-либо изменений в функцииcheckForComodification()
.
А теперь давайте закомментируем часть кода с ошибкой, запустим программу еще раз и увидим следующий результат:
1 2 3 |
Map Value:3 Map Value:2 Map Value:4 |
Поскольку мы обновляем существующий элемент в MyMap, его размер не изменился, следовательно мы не получаем ConcurrentModificationException
. Обратите внимание, что результат выполнения программы может отличаться в вашей системе, потому что HashMap набор ключей (keyset
) не упорядочен как список. Если вы раскомментировать ту часть кода, где мы добавляем новый элемент в HashMap
, то это вызовет ConcurrentModificationException
.
А теперь практические советы:
Как избежать ConcurrentModificationException в многопоточной среде
- Вы можете конвертировать список в массив, и работать с массивом. Этот подход хорошо работает для малого и среднего размера списка, но если список большой, то это будет сильно влиять на производительность.
- Вы можете заблокировать список на время обхода элементов, вставив его в синхронизированный блок. Этот подход не рекомендуется, потому что это попросту убьет преимущества многопоточности.
- Вы можете использовать классы
ConcurrentHashMap
иCopyOnWriteArrayList
. Это самый эффективный и правильный подход.
Как избежать ConcurrentModificationException в однопоточной среде
В многопоточной среде вы можете использовать метод remove
()
, чтобы удалить объект. Но в этом случае вы можете удалить только текущий объект, а не любой другой объект из списка.
Давайте рассмотрим на примере
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
package ua.com.prologistic; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; public class ThreadSafeIteratorExample { public static void main(String[] args) { List<String> myList = new CopyOnWriteArrayList<String>(); myList.add("1"); myList.add("2"); myList.add("3"); myList.add("4"); myList.add("5"); Iterator<String> it = myList.iterator(); while(it.hasNext()){ String value = it.next(); System.out.println("List Value:"+value); if(value.equals("3")){ myList.remove("4"); myList.add("6"); myList.add("7"); } } System.out.println("List Size:"+myList.size()); Map<String,String> myMap = new ConcurrentHashMap<String,String>(); myMap.put("1", "1"); myMap.put("2", "2"); myMap.put("3", "3"); Iterator<String> it1 = myMap.keySet().iterator(); while(it1.hasNext()){ String key = it1.next(); System.out.println("Map Value:"+myMap.get(key)); if(key.equals("1")){ myMap.remove("3"); myMap.put("4", "4"); myMap.put("5", "5"); } } System.out.println("Map Size:"+myMap.size()); } } |
Результат будет такой:
1 2 3 4 5 6 7 8 9 10 11 |
List Value:1 List Value:2 List Value:3 List Value:4 List Value:5 List Size:6 Map Value:1 Map Value:null Map Value:4 Map Value:2 Map Size:4 |
Выводы
- Потокобезопасные коллекции позволяют избежать
ConcurrentModificationException
- В случае CopyOnWriteArrayList, итератор будет работать с начальным списком.
- В случае ConcurrentHashMap, поведение не всегда одинаковое.
Результатом выполнения такого кода:
1 2 3 4 |
if(key.equals("1")){ myMap.remove("3"); ... } |
Будет следующее:
1 2 3 4 5 |
Map Value:1 Map Value:null Map Value:4 Map Value:2 Map Size:4 |
Если изменить код выше на такое:
1 2 3 4 |
if(key.equals("3")){ myMap.remove("2"); ... } |
То получим следующее:
1 2 3 4 |
Map Value:1 Map Value:3 Map Value:null Map Size:4 |
Теперь Вы знаете как избежать ConcurrentModificationException при использовании Iterator.
Следите за обновлениями на javadevblog.com.