ConcurrentModificationException with ConcurrentHashMap

Viewed 1772

I understand the concept behind it but thought using ConcurrentHashMap instead of HashMap will fix it. Because ConcurrentHashMap protects from concurrent reading and modification by different threads.

But I still see the exception.

Here's the code snippet-

SampleFile.java

prepareInfo(RequestHelper.getSender(request), someVar, concurrentMap);
....
...
    private void prepareInfo(final Sender sender, final SomeVar someVar, final ConcurrentHashMap<String,
            Object> concurrentMap){
        final Info info = RequestHelper.getInfo(someVar);
        someVar.setInfo(info);
        if(sender != null){
            prepareProfileInfo(sender.getUserDetails(), info, concurrentMap);
            mapDetailsWithMap(sender.getDetails(), concurrentMap);
            if(sender.getSenderId() != null){
                concurrentMap.put("sender_id", sender.getSenderId());
            }
            concurrentMap.putAll(sender.getAdditionalProperties());
        }
    }

The error stacktrace is -

    at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445)
    at java.util.HashMap$EntryIterator.next(HashMap.java:1479)
    at java.util.HashMap$EntryIterator.next(HashMap.java:1477)
    at java.util.concurrent.ConcurrentHashMap.putAll(ConcurrentHashMap.java:1083)
    at SampleFile.prepareAccountInfo(SampleFile.java:114)

Couple of questions I am not clear about -

  1. Why is the exception still happening?
  2. Is there a way we can test the fix via Unit Test?
2 Answers

This line retrieves a HashMap with sender.getAdditionalProperties() and then iterates on the HashMap, adding each item to concurrentMap:

        concurrentMap.putAll(sender.getAdditionalProperties());

If the HashMap within sender is modified while the iteration runs, you will get a ConcurrentModificationException. The exception means "the structure of the map was modified while I was iterating on it, so I don't know what to do now".

To allow concurrent modification and iteration on the map inside the sender object, that map should be a ConcurrentHashMap.

To test the fix, you can make a test that does the following:

Map<String,Object> map =  sender.getAdditionalProperties()
map.put("foo", "bar");
Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator();
// uh-oh - adding an item invalidates HashMap iterator
map.put("bar", "baz");
// Throws exception for HashMap
iterator.next();

You've made an understandable and very common mistake.

You saw ConcurrentModificationException, and you thought: Huh. Apparently, threads.

But that's wrong. The name is... unfortunate, perhaps.

ConcurrentModificationException has nothing whatsoever to do with threading.

CoModEx simply means that this sequence of events has occured:

  1. You have made an iterator out of a collection, e.g. by calling .iterator(), or by writing for (var x : someCollection) which makes one.
  2. At some point in time after you made that iterator, you change the underlying collection in some way. You cleared it, added something to it, updated a value, or removed something. any change at all.
  3. You do anything with the iterator made in step 1, such as calling .hasNext() or next() on it, or your for (var x : someCollection) gets to the closing brace / you run a continue.

Then, boom. CoModEx.

Here's a trivial way to do it. Note how this app is entirely singlethreaded:

class ThisAsplodes {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("Okay");
        for (String elem : list) {
            list.add("Bye");
        }
    }
}

compile it, run it, and, voila. ConcurrentModificationException. The list is modified ("Bye" is added), and then iteration occurs on an iterator made prior to the change (the closing brace).

It gets even better: If you try to perform this sequence of events with a plain jane hashmap and multiple threads (one thread makes the iterator, another modifies it), then CoModEx could happen, but more usually things just break in weird ways. a key you just added appears not to be in the map, for example. With ConcurrentHashMap, you get the CoModEx reliably.

It is not obvious from your code where you're doing this, but at least now you know where to look.

Related