문제

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
        } 
    }
도움이 되었습니까?

해결책 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.

다른 팁

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!

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top