Подтвердить что ты не робот

ConcurrentModificationException с LinkedHashMap

Не уверен, что запускает java.util.ConcurrentModificationException, когда я перебираю структуру LinkedHashMap в приведенном ниже коде. Использование подхода Map.Entry работает отлично. Не получил хорошее объяснение того, что вызывает это из предыдущих сообщений.

Любая помощь будет оценена.

import java.util.LinkedHashMap;
import java.util.Map;

public class LRU {

    // private Map<String,Integer> m = new HashMap<String,Integer>();
    // private SortedMap<String,Integer> lru_cache = Collections.synchronizedSortedMap(new TreeMap<String, Integer>());

    private static final int MAX_SIZE = 3;

    private LinkedHashMap<String,Integer> lru_cache = new LinkedHashMap<String,Integer>(MAX_SIZE, 0.1F, true){
        @Override
        protected boolean removeEldestEntry(Map.Entry eldest) {
            return(lru_cache.size() > MAX_SIZE);
         }
    };    

    public Integer get1(String s){
        return lru_cache.get(s);        
    }

    public void displayMap(){
        /**
         * Exception in thread "main" java.util.ConcurrentModificationException
            at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(LinkedHashMap.java:373)
            at java.util.LinkedHashMap$KeyIterator.next(LinkedHashMap.java:384)
            at LRU.displayMap(LRU.java:23)
            at LRU.main(LRU.java:47)
         */
        *for(String key : lru_cache.keySet()){
            System.out.println(lru_cache.get(key));
        }*

// This parser works fine        
//        for(Map.Entry<String, Integer> kv : lru_cache.entrySet()){
//            System.out.println(kv.getKey() + ":" + kv.getValue());
//        }
    }

    public void set(String s, Integer val){
        if(lru_cache.containsKey(s)){            
            lru_cache.put(s, get1(s) + val);
        }
        else{
            lru_cache.put(s, val);
        }
    }

    public static void main(String[] args) {

        LRU lru = new LRU();
        lru.set("Di", 1);
        lru.set("Da", 1);
        lru.set("Daa", 1);
        lru.set("Di", 1);        
        lru.set("Di", 1);
        lru.set("Daa", 2);
        lru.set("Doo", 2);
        lru.set("Doo", 1);        
        lru.set("Sa", 2);
        lru.set("Na", 1);
        lru.set("Di", 1);
        lru.set("Daa", 1);

        lru.displayMap();

    }

}
4b9b3361

Ответ 1

Прочитайте Javadoc для LinkedHashMap:

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

Поскольку вы передаете true в конструктор LinkedHashMap, он находится в порядке доступа и когда вы пытаетесь get что-то из него, вы его структурно модифицируете.

Также обратите внимание, что при использовании расширенного синтаксиса for вы фактически используете итератор. Упрощенная цитата из JLS §14.14.2:

Усовершенствованный оператор for имеет следующую форму:

EnhancedForStatement:

for ( TargetType Identifier : Expression ) Statement

[...]

Если тип выражения является подтипом Iterable<X> для некоторого типа аргумент X, то пусть I - тип java.util.Iterator<X>; в противном случае, пусть I является необработанным типом java.util.Iterator.

Усовершенствованный оператор for эквивалентен базовому выражению forформа:

for (I #i = Expression.iterator(); #i.hasNext(); ) {
     TargetType Identifier =
         (TargetType) #i.next();
     Statement
}

#i - это автоматически сгенерированный идентификатор, отличный от любых других идентификаторов (автоматически генерируемых или иным образом), которые находятся в scope (§6.3) в точке, где происходит расширение для утверждения.

Кроме того, в Javadoc для LinkedHashMap:

Итераторы, возвращаемые методом iterator коллекций, возвращаемые всеми методами просмотра класса, являются fail-fast: если карта структурно изменена в любое время после создания итератора, любым способом, кроме как через итератор remove, итератор будет ConcurrentModificationException.

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

for (Integer i : lru_cache.values()) {
    System.out.println(i);
}

Ответ 2

Вы используете привязанную к доступу хэш-карту: из спецификации http://docs.oracle.com/javase/7/docs/api/java/util/LinkedHashMap.html,

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

Просто вызов get достаточно, чтобы считаться структурной модификацией, вызывая исключение. Если вы используете последовательность entrySet(), вы запрашиваете только запись и НЕ карту, поэтому вы не запускаете ConcurrentModificationException.

Ответ 3

В конструкторе LinkedHashMap вы передаете true, чтобы получить поведение LRU (это означает, что политика выселения порядок доступа, а не false для порядок вставки).

Таким образом, каждый раз, когда вы вызываете get(key), базовый Map.Entry увеличивает счетчик доступа и переупорядочивает коллекцию, перемещая (последний доступ) Map.Entry в начало списка.

Итератор (неявно созданный циклом for) проверяет измененный флаг, который отличается от оригинала, который первоначально выполнялся, поэтому выбрасывает ConcurrentModificationException.

Чтобы избежать этого, вы должны использовать entrySet(), поскольку реализация наследуется от java.util.HashMap, и поэтому итератор не проверяет флаги модификации:

    for(Map.Entry<String,Integer> e : lru_cache.entrySet()){
        System.out.println(e.getValue());
    }

Помните, что этот класс не является потокобезопасным, поэтому в параллельных средах вам нужно будет использовать потенциально дорогостоящие охранники, такие как Collections.synchronizedMap(Map). В этом случае лучшим вариантом может быть Google Guava Cache.

Ответ 4

java.util.ConcurrentModificationException: Если в этом списке есть какие-либо структурные изменения (добавления, удаления, переименование и т. Итератор проверяет, изменился ли список перед каждой операцией. Это называется "безотказной работой".

Если поток изменяет коллекцию напрямую, когда он выполняет итерацию по коллекции с быстрым итератором, итератор будет генерировать это исключение. Здесь вы не можете вызвать метод get() при использовании итератора, потому что вызов get() структурно изменяет карту, и, следовательно, следующий вызов одного из методов итератора завершается с ошибкой и выдает ConcurrentModificationException.

Ответ 5

Ваш код

for(String key : lru_cache.keySet()){
    System.out.println(lru_cache.get(key));
}

Собственно компилируется:

Iterator<String> it = lru_cache.keySet().iterator();
while (it.hasNext()) {
    String key = it.next();
    System.out.println(lru_cache.get(key));
}

Затем ваш LRU-кеш сжимается до элементов MAX_SIZE не при вызове set(), но при вызове get() - выше ответы объясняют, почему.

Таким образом, мы имеем следующее поведение:

  • новый итератор, созданный для итерации по lru_cache.keySet() коллекции
  • lru_cache.get() вызывается для извлечения элемента из кэша
  • get() invocation усекает lru_cache в MAX_SIZE элементы (в вашем случае 3)
  • Итератор it становится недействительным из-за модификации коллекции и выбрасывает следующую итерацию.

Ответ 6

Это связано с отказоустойчивым поведением рамки коллекций также при изменении списка (путем добавления или удаления элементов), в то время как перемещение списка с этой ошибкой будет там с Iterator. Я наткнулся на эту ошибку некоторое время назад. Для получения подробной информации см. Ниже темы.

ConcurrentModificationException при добавлении внутри цикла foreach в ArrayList

Несмотря на то, что это говорит о списке массивов, он применяется для большинства наборов данных коллекции (ов).

Параллельная модификация Исключение: добавление в ArrayList

http://docs.oracle.com/javase/6/docs/api/java/util/ConcurrentModificationException.html