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

▼魔方 西西 提交于 2019-12-04 06:46:14

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.

I would use ConcurrentHashMap instead of a HashMap and protect it from concurrent reading and modification by different threads. See the below implementation. It is not possible for thread 1 and thread 2 to read and write at the same time. When thread 1 is extracting values from Map to an array, all other threads that invoke storeInMap(K, V) will suspend and wait on the map until the first thread is done with the object.

Note: I do not use synchronized method in this context; I do not completely rule out synchronized method but I would use it with caution. A synchronized method is actually just syntax sugar for getting the lock on 'this' and holding it for the duration of the method so it can hurt throughput.

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);
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!