How to avoid HashMap "ConcurrentModificationException" while manipulating `values()` and `put()` in concurrent threads?

hengxin picture hengxin · Oct 29, 2014 · Viewed 22.9k times · Source

Code:

I have a HashMap

private Map<K, V> map = new HashMap<>();

One method will put K-V pair into it by calling put(K,V).

The other method wants to extract a set of random elements from its values:

int size = map.size();    // size > 0
V[] value_array = map.values().toArray(new V[size]);
Random rand = new Random();
int start = rand.nextInt(size); int end = rand.nextInt(size);
// return value_array[start .. end - 1]

The two methods are called in two different concurrent threads.


Error:

I got a ConcurrentModificationException error:

at java.util.HashMap$HashIterator.nextEntry(Unknown Source)
at java.util.HashMap$ValueIterator.next(Unknown Source)
at java.util.AbstractCollection.toArray(Unknown Source)

It seems that the toArray() method in one thread is actually iterating over the HashMap and a put() modification in other thread occurs.

Question: How to avoid "ConcurrentModificationException" while using HashMap.values().toArray() and HashMap.put() in concurrent threads?
Directly avoiding using values().toArray() in the second method is also OK.

Answer

Ted Hopp picture Ted Hopp · Oct 29, 2014

You need to provide some level of synchronization so that the call to put is blocked while the toArray call is executing and vice versa. There are three two simple approaches:

  1. Wrap your calls to put and toArray in synchronized blocks that synchronize on the same lock object (which might be the map itself or some other object).
  2. Turn your map into a synchronized map using Collections.synchronizedMap()

    private Map<K, V> map = Collections.synchronizedMap(new HashMap<>());
    

  3. Use a ConcurrentHashMap instead of a HashMap.

EDIT: The problem with using Collections.synchronizedMap is that once the call to values() returns, the concurrency protection will disappear. At that point, calls to put() and toArray() might execute concurrently. A ConcurrentHashMap has a somewhat similar problem, but it can still be used. From the docs for ConcurrentHashMap.values():

The view's iterator is a "weakly consistent" iterator that will never throw ConcurrentModificationException, and guarantees to traverse elements as they existed upon construction of the iterator, and may (but is not guaranteed to) reflect any modifications subsequent to construction.