Question

I have the code below but I'm getting ConcurrentModificationException, how should I avoid this issue? (I have to use WeakHashMap for some reason)

WeakHashMap<String, Object> data = new WeakHashMap<String, Object>();

 // some initialization code for data

  for (String key : data.keySet()) {
        if (data.get(key) != null && data.get(key).equals(value)) {
            //do something to modify the key
        } 
    }
Était-ce utile?

La solution 4

Probably because your // do something in the iteration is actually modifying the underlying collection.

From ConcurrentModificationException:

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.

And from (Weak)HashMap's keySet():

Returns a Set view of the keys contained in this map. The set is backed by the map, so changes to the map are reflected in the set, and vice-versa. If the map is modified while an iteration over the set is in progress (except through the iterator's own remove operation), the results of the iteration are undefined.

Autres conseils

The Javadoc for WeakHashMap class explains why this would happen:

Map invariants do not hold for this class. Because the garbage collector may discard keys at any time, a WeakHashMap may behave as though an unknown thread is silently removing entries

Furthermore, the iterator generated under the hood by the enhanced for-loop you're using is of fail-fast type as per quoted explanation in that javadoc.

The iterators returned by the iterator method of the collections returned by all of this class's "collection view methods" are fail-fast: if the map is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove method, the iterator will throw a ConcurrentModificationException. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

Therefore your loop can throw this exception for these reasons:

  1. Garbage collector has removed an object in the keyset.
  2. Something outside the code added an object to that map.
  3. A modification occurred inside the loop.

As your intent appears to be processing the objects that are not GC'd yet, I would suggest using an iterator as follows:

Iterator<String> it = data.keySet().iterator();
int count = 0;
int maxTries = 3;
while(true) {
    try {
        while (it.hasNext()) {
            String str = it.next();
            // do something
        }
        break;
    } catch (ConcurrentModificationException e) {
        it = data.keySet().iterator(); // get a new iterator
        if (++count == maxTries) throw e;
    }
}

You can clone the key set first, but note that you hold the strong reference after that:

Set<KeyType> keys;
while(true) {
   try {
       keys = new HashSet<>(weakHashMap.keySet());
       break;
   } catch (ConcurrentModificationException ignore) {
   }
}

for (KeyType key : keys) {
    // ...
}

WeakHashMap's entries are automatically removed when no ordinary use of the key is realized anymore, this may happens in a different thread. While cloning the keySet() into a different Set a concurrent Thread may remove entries meanwhile, in this case a ConcurrentModificationException will 100% be thrown! You must synchronize the cloning.

Example:

Collections.synchronizedMap(data);

Please understand that

Collections.synchronizedSet(data.keySet());

Can not be used because data.keySet() rely on data's instance who is not synchronized here! More detail: synchronize(keySet) prevents the execution of methods on the keySet but keySet's remove-method is never called but WeakHashMap's remove-method is called so you have to synchronize over WeakHashMap!

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top