ConcurrentModificationException with LinkedHashMap

后端 未结 6 663
北恋
北恋 2021-02-04 03:09

Not sure what is triggering a java.util.ConcurrentModificationException when I iterate over the LinkedHashMap structure in the code below. Using the

相关标签:
6条回答
  • 2021-02-04 03:21

    Read the Javadoc for LinkedHashMap:

    A structural modification is any operation that adds or deletes one or more mappings or, in the case of access-ordered linked hash maps, affects iteration order. In insertion-ordered linked hash maps, merely changing the value associated with a key that is already contained in the map is not a structural modification. In access-ordered linked hash maps, merely querying the map with get is a structural modification.

    Since you're passing in true to the LinkedHashMap constructor, it is in access order and when you are trying to get something from it, you are structurally modifying it.

    Also note that when you use the enhanced for syntax, you are actually using an iterator. Simplified quote from JLS §14.14.2:

    The enhanced for statement has the form:

    EnhancedForStatement:
    
    for ( TargetType Identifier : Expression ) Statement
    

    [...]

    If the type of Expression is a subtype of Iterable<X> for some type argument X, then let I be the type java.util.Iterator<X>; otherwise, let I be the raw type java.util.Iterator.

    The enhanced for statement is equivalent to a basic for statement of the form:

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

    #i is an automatically generated identifier that is distinct from any other identifiers (automatically generated or otherwise) that are in scope (§6.3) at the point where the enhanced for statement occurs.

    Also, in the Javadoc for LinkedHashMap:

    The iterators returned by the iterator method of the collections returned by all of this class's collection view methods are fail-fast: if the map is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove method, the iterator will throw a ConcurrentModificationException.

    Therefore, when you are calling get on the map, you are performing structural modifications to it, causing the iterator in the enhanced-for to throw an exception. I think you meant to do this, which avoids calling get:

    for (Integer i : lru_cache.values()) {
        System.out.println(i);
    }
    
    0 讨论(0)
  • 2021-02-04 03:27

    Your code

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

    Actually compiles to:

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

    Next, your LRU cache shrinks itself to MAX_SIZE elements not when calling set(), but when calling get() - above answers explain why.

    Thus we have following behavior:

    • new iterator created to iterate over lru_cache.keySet() collection
    • lru_cache.get() called to extract element from your cache
    • get() invocation truncates lru_cache to MAX_SIZE elements (in your case 3)
    • iterator it becomes invalid due to collection modification and throws on next iteration.
    0 讨论(0)
  • 2021-02-04 03:36

    java.util.ConcurrentModificationException : If there are any structural changes (additions, removals, rehashing, etc.) to the underlying list while the iterator exists. The iterator checks to see if the list has changed before each operation. This is known as 'failsafe operation'.

    If a thread modifies a collection directly while it is iterating over the collection with a fail-fast iterator, the iterator will throw this exception.Here you cannot call the get() method while using an iterator because calling get() structurally modifies the map and hence the next call to one of the iterators method fails and throws a ConcurrentModificationException.

    0 讨论(0)
  • 2021-02-04 03:40

    In the constructor of LinkedHashMap you pass true to get the LRU behaviour (meaning the eviction policy is access order rather than false for insertion order).

    So every time you call get(key) the underlying Map.Entry increments an access counter AND reorders the collection by moving the (last accessed) Map.Entry to the head of the list.

    The iterator (implicitly created by the for loop) checks the modified flag, which is different from the copy it took originally, so throws the ConcurrentModificationException.

    To avoid this you should use the entrySet() as the implementation is inherited from java.util.HashMap and therefore the iterator doesn't check the modification flags:

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

    Be aware this class isn't threadsafe so in concurrent environments you will need to use an potentially expensive guards like Collections.synchronizedMap(Map). In this scenario a better option might be Google's Guava Cache.

    0 讨论(0)
  • 2021-02-04 03:44

    You're using an access-ordered linked hash map: from the spec at http://docs.oracle.com/javase/7/docs/api/java/util/LinkedHashMap.html,

    A structural modification is any operation that adds or deletes one or more mappings or, in the case of access-ordered linked hash maps, affects iteration order. In insertion-ordered linked hash maps, merely changing the value associated with a key that is already contained in the map is not a structural modification. In access-ordered linked hash maps, merely querying the map with get is a structural modification.)

    Simply calling get is enough to be considered a structural modification, triggering the exception. If you use the entrySet() sequence you're only querying the entry and NOT the map, so you don't trigger the ConcurrentModificationException.

    0 讨论(0)
  • 2021-02-04 03:45

    It is coz of fail-fast behaviour of collections framework also when you modify the list (by adding or removing elements) while traversing a list with this error will be there with Iterator. I came across this error some time back . Refer below threads to for detail info.

    ConcurrentModificationException when adding inside a foreach loop in ArrayList

    Though this says array list, it applies for most of the collection(s) data strucutres.

    Concurrent Modification Exception : adding to an ArrayList

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

    0 讨论(0)
提交回复
热议问题