2013-04-23 11 views
13

Nie jestem pewien, co jest wyzwalanie java.util.ConcurrentModificationException, gdy przejdę do struktury LinkedHashMap w poniższym kodzie. Korzystanie z podejścia Map.Entry działa dobrze. Nie otrzymałem dobrego wyjaśnienia, co wywołuje to z poprzednich postów.ConcurrentModificationException z LinkedHashMap

Każda pomoc zostanie doceniona.

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(); 

    } 

} 
+2

Czy naprawdę chciałeś mieć współczynnik obciążenia wynoszący 0,1 podczas ustawiania początkowej pojemności na MAX_SIZE. Czynnik obciążenia zapewnia co najmniej pojemność wielkości()/współczynnika obciążenia. to znaczy, gdy masz 3 klucze, będziesz mieć pojemność co najmniej 30 (w rzeczywistości 32, ponieważ musi to być moc 2). Sugeruję użycie 'MAX_SIZE * 10/7' i domyślnego współczynnika obciążenia' 0,7f', który da w tym przypadku pojemność 4. –

Odpowiedz

8

Używasz dostępu zamówić umieszczonego skrótu mapę: od spec w http://docs.oracle.com/javase/7/docs/api/java/util/LinkedHashMap.html,

modyfikacja strukturalna jest każda operacja, która dodaje lub usuwa jeden lub więcej mapowania lub w przypadek powiązanych hash map połączonych, wpływa na kolejność iteracji. W połączonych ze sobą mapach haszowanych, tylko zmiana wartości skojarzonej z kluczem, który jest już zawarty w , mapa nie jest modyfikacją strukturalną. W dostępie zamówić połączonych mapy hash, jedynie zapytań mapę z GET jest strukturalnym modyfikacji.)

prostu dzwoniąc get wystarczy rozważyć modyfikację strukturalną, wywołując wyjątek. Jeśli używasz sekwencji entrySet(), wpisujesz tylko wpis, a NIE mapę, więc nie uruchamiasz ConcurrentModificationException.

31

odczytać Javadoc for LinkedHashMap:

Modyfikacja strukturalny jest każda operacja, która dodaje lub usuwa jeden lub więcej mapowania lub, w przypadku dostępu zamówić połączonych map hash, wpływa na kolejność iteracji. W połączonych ze sobą mapach haszowanych, tylko zmiana wartości skojarzonej z kluczem, który jest już zawarty w , mapa nie jest modyfikacją strukturalną. W połączonych ze sobą mapach hash połączonych z dostępem po prostu zapytanie do mapy za pomocą get jest modyfikacją strukturalną .

Ponieważ jesteś przejazdem w true do konstruktora LinkedHashMap, to w celu dostępu i kiedy próbują get czegoś od niego, to są strukturalnie modyfikacji.

Należy również pamiętać, że podczas korzystania z rozszerzonej składni for, w rzeczywistości używa się iteratora. Uproszczone cytat z JLS §14.14.2:

Udoskonalony for oświadczenie ma postać:

EnhancedForStatement: 

for (TargetType Identifier : Expression) Statement 

[...]

Jeśli typ Expression jest podtypem Iterable<X> jakiegoś rodzaju argument X, następnie niech I będzie typu java.util.Iterator<X>; w przeciwnym razie, niech I będzie typu surowego java.util.Iterator.

Zwiększona for zestawienie odpowiada podstawowej for zestawienia postać:

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

#i jest automatycznie generowany identyfikatorem, który różni się od innych identyfikatorów (generowanych automatycznie lub w inny sposób), które znajdują się w zakres (§6.3) w punkcie, w którym występuje poprawiona do stwierdzenia.

Również w Javadoc dla LinkedHashMap:

W iteratory zwracane przez metody zbiorów zwróconych przez cały widok zbiór metod tej klasy za iteratorfail-szybka: jeżeli mapa jest modyfikowana strukturalnie w dowolnym momencie po utworzeniu iteratora, w dowolny sposób, z wyjątkiem iteratora, za pomocą iteratora, który wyrzuci ConcurrentModificationException.

Dlatego też, gdy dzwonisz get na mapie, jesteś wykonywania zmian konstrukcyjnych do niego, powodując iterator we wzmocnionej-for rzucić wyjątek. Myślę, że oznaczało to zrobić, który unika wywoływania get:

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

w konstruktorze LinkedHashMap mijamy true aby uzyskać zachowanie LRU (czyli politykę eksmisji jest kolejność dostępu zamiast false dla wkładania zamówienie).

Za każdym razem, gdy wywoływana jest get(key), podstawowa mapa.Entry zwiększa licznik dostępu ORAZ zmienia kolejność kolekcji, przenosząc (ostatnio dostępny) Map.Entry do nagłówka listy.

Iterator (niejawnie utworzony przez pętlę for) sprawdza zmodyfikowaną flagę, która różni się od pierwotnie skopiowanej kopii, więc generuje ConcurrentModificationException.

Aby tego uniknąć należy użyć entrySet() jako realizacja jest dziedziczona z java.util.HashMap a więc iterator nie sprawdza flagi zmiany:

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

Należy pamiętać, klasa ta nie jest threadsafe więc w środowiskach współbieżnych będziesz musiał użyć potencjalnie drogich strażników, takich jak Collections.synchronizedMap (Map). W tym scenariuszu lepszym rozwiązaniem może być Google's Guava Cache.

1

Kod

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

Właściwie kompiluje do:

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

Następnie pamięci podręcznej LRU kurczy się do MAX_SIZE elementy nie Dzwoniąc set(), ale podczas wywoływania get() - powyżej odpowiedzi wyjaśnić dlaczego.

Mamy więc następujące zachowania:

  • nowy iterator stworzony iteracyjne nad lru_cache.keySet() kolekcji
  • lru_cache.get() nazywa wyodrębnić elementy z pamięci podręcznej
  • get() inwokacja obcina lru_cache do MAX_SIZE elementów (w przypadku 3)
  • iterator it staje się nieważny z powodu modyfikacji kolekcji i wyrzuca w następnej iteracji.
1

Jest to niezbyt szybkie zachowanie struktury kolekcji również podczas modyfikowania listy (przez dodawanie lub usuwanie elementów) podczas przechodzenia listy z tym błędem będzie tam z Iteratorem. Natknąłem się na ten błąd jakiś czas temu. Poniższe wątki zawierają szczegółowe informacje.

ConcurrentModificationException when adding inside a foreach loop in ArrayList

Choć mówi listy tablicy, ma ona zastosowanie do większości zbiór (y) strucutres danych.

Concurrent Modification Exception : adding to an ArrayList

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

2

java.util.ConcurrentModificationException: Jeśli istnieją jakiekolwiek zmiany strukturalne (dodatki, przeprowadzki, hashuje, itd.) Na liście podstawowej, podczas gdy iterator istnieje. Iterator sprawdza, czy lista zmieniła się przed każdą operacją. Jest to znane jako "operacja bezpieczna dla awarii".

Jeśli wątek modyfikuje kolekcję bezpośrednio podczas jego iteracji po kolekcji z fail-fast iterator, iterator rzuci ten exception.Here nie można wywołać metodę get() podczas korzystania iterator ponieważ nazywając get() strukturalnie modyfikuje mapę i dlatego kolejne wywołanie jednej z metod iteratorów kończy się niepowodzeniem i powoduje wyświetlenie ConcurrentModificationException.

Powiązane problemy