How to avoid the HashMap "ConcurrentModificationException" when manipulating `values โ€‹โ€‹()` and `put ()` in parallel threads?

the code:

I have a hashmap

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

One method will put a pair of KV in it, calling put(K,V) .

Another method wants to extract a set of random elements from their 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] 

Two methods are called in two different parallel 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 actually iterates over the HashMap, and put() is being modified in another thread.

Question: How to avoid "ConcurrentModificationException" when using HashMap.values โ€‹โ€‹(). toArray () and HashMap.put () in parallel threads?
Directly avoiding the use of values().toArray() in the second method is also good.

+5
source share
2 answers

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

  • Exchange your calls to put and toArray in synchronized blocks that are synchronized on the same lock object (which may be the card itself or some other object).
  • Turn your card into a synchronized card using Collections.synchronizedMap()

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

    hit>

  • Use ConcurrentHashMap instead of HashMap .

EDIT: The problem with using Collections.synchronizedMap is that after returning the call to values() , the concurrency protection will disappear. At this point, calls to put() and toArray() can be executed at the same time. A ConcurrentHashMap has a somewhat similar problem, but it can still be used. From the docs for ConcurrentHashMap.values() :

A view iterator is a โ€œweakly matchedโ€ iterator that will never throw a ConcurrentModificationException and ensures that the elements intersect as they existed during the construction of the iterator, and can (but not guaranteed) reflect any changes after construction.

+4
source

I would use ConcurrentHashMap instead of HashMap and protect it from being read and modified simultaneously by various threads. See the following implementation. It is not possible to read and write thread 1 and thread 2 at the same time. When thread 1 retrieves the values โ€‹โ€‹from Map into an array, all other threads that call storeInMap (K, V) will pause and wait on the map until the first thread is executed with the object.

Note. I do not use the synchronous method in this context; I do not completely exclude the synchronized method, but I will use it with caution. A synchronized method is actually just syntactic sugar for getting a lock on 'this' and holding it for the duration of the method so that it can damage the bandwidth.

 private Map<K, V> map = new ConcurrentHashMap<K, V>(); // thread 1 public V[] pickRandom() { int size = map.size(); // size > 0 synchronized(map) { 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] } // thread 2 public void storeInMap(K, V) { synchronized(map) { map.put(K,V); } } 
0
source

Source: https://habr.com/ru/post/1205746/


All Articles