superbadcodemonkey
superbadcodemonkey

Reputation: 917

ConcurrentModificationException when using iterator to remove entry

I have a simple piece of code that loops through a map, checks a condition for each entry, and executes a method on the entry if that condition is true. After that the entry is removed from the map. To delete an entry from the map I use an Iterator to avoid ConcurrentModificationException's.

Except my code does throw an exception, at the it.remove() line:

Caused by: java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.remove(Unknown Source) ~[?:1.8.0_161]
    at package.Class.method(Class.java:34) ~[Class.class:?]

After a long search I can't find a way to fix this, all answers suggest using the Iterator.remove() method, but I'm already using it. The documentation for Map.entrySet() clearly specifies that it is possible to remove elements from the set using the Iterator.remove() method.

Any help would be greatly appreciated.

My code:

Iterator<Entry<K, V>> it = map.entrySet().iterator();
while (it.hasNext()) {
    Entry<K, V> en = it.next();

    if (en.getValue().shouldRun()) {
        EventQueue.invokeLater(()->updateSomeGui(en.getKey())); //the map is in no way modified in this method
        en.getValue().run();
        it.remove(); //line 34
    }
}

Upvotes: 4

Views: 2344

Answers (4)

Corn&#233;lio Sousa
Corn&#233;lio Sousa

Reputation: 45

Since I'm already here and there isn't a "complete" answer, there it goes:

The HashMap class documentation explicitly says that if multiple threads access a HashMap instance and at least one modifies it structurally, the HashMap instance must be synchronized externally (usually using Collections.synchronizedMap).

Note that this implementation is not synchronized. If multiple threads access a hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally. (A structural modification is any operation that adds or deletes one or more mappings; merely changing the value associated with a key that an instance already contains is not a structural modification.) This is typically accomplished by synchronizing on some object that naturally encapsulates the map. If no such object exists, the map should be "wrapped" using the Collections.synchronizedMap method.

It also says that changing the value associated with a key isn't a structural modification, but doesn't say if those cases always produces a defined behavior (probably race conditions occur, IDK).

Now for the ConcurrentModificationException. It is raised by a fail-fast iterator when it can tell that a structural modification was performed it the backing Map without being made through this specific iterator remove() method. That said, iterators in single thread programs can also raise this exception when you alter the backing Map while the iterator is alive if this modification isn't performed using the iterator remove().

From the ConcurrentModificationException documentation:

Note that this exception does not always indicate that an object has been concurrently modified by a different thread. If a single thread issues a sequence of method invocations that violates the contract of an object, the object may throw this exception. For example, 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.

In your case, if a thread in your program instantiate an iterator from a HashMap instance just for reading, and another thread (or even the same thread) also instantiate an iterator from the same Map instance and the lifetime of those iterators overlap, if one iterator calls remove(), then the other can raise ConcurrentModificationException.

Upvotes: 0

Seeker
Seeker

Reputation: 957

For such purposes you should use the collection views a map exposes:

keySet() lets you iterate over keys. That won't help you, as keys are usually immutable.

values() is what you need if you just want to access the map values. If they are mutable objects, you can change directly, no need to put them back into the map.

entrySet() the most powerful version, lets you change an entry's value directly.

Example: convert the values of all keys that contain an upperscore to uppercase

for(Map.Entry<String, String> entry:map.entrySet()){
    if(entry.getKey().contains("_"))
        entry.setValue(entry.getValue().toUpperCase());
}

Actually, if you just want to edit the value objects, do it using the values collection. I assume your map is of type <String, Object>:

for(Object o: map.values()){
    if(o instanceof MyBean){
        ((Mybean)o).doStuff();
    }
}

Upvotes: -1

aurelius
aurelius

Reputation: 4076

If you cannot change HashMap to ConcurrentHashMap you can use another approach to your code.

You can create a list of entries containing the entries that you want to delete and then iterate over them and remove them from the original map.

e.g.

    HashMap<String, String> map = new HashMap<>();
    map.put("1", "a1");
    map.put("2", "a2");
    map.put("3", "a3");
    map.put("4", "a4");
    map.put("5", "a5");
    Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
    List<Map.Entry<String, String>> entries = new ArrayList<>();

    while (iterator.hasNext()) {
        Map.Entry<String, String> next = iterator.next();
        if (next.getKey().equals("2")) {
            /* instead of remove
            iterator.remove();
            */
            entries.add(next);
        }
    }

    for (Map.Entry<String, String> entry: entries) {
        map.remove(entry.getKey());
    }

Upvotes: 2

Rohit Dodle
Rohit Dodle

Reputation: 376

Please use ConcurrentHashMap in place of HashMap as you are acting on the object in multiple threads. HashMap class isn't thread safe and also doesn't allow such operation. Please refer below link for more information related to this.

https://www.google.co.in/amp/s/www.geeksforgeeks.org/difference-hashmap-concurrenthashmap/amp/

Let me know for more information.

Upvotes: 1

Related Questions