Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ConcurrentModificationException when using stream with Maps key set

Tags:

I want to remove all items from someMap which keys are not present in someList. Take a look into my code:

someMap.keySet().stream().filter(v -> !someList.contains(v)).forEach(someMap::remove); 

I receive java.util.ConcurrentModificationException. Why? Stream is not parallel. What is the most elegant way to do this?

like image 912
Mariusz Jaskółka Avatar asked Sep 15 '15 08:09

Mariusz Jaskółka


People also ask

How do I fix ConcurrentModificationException in Java?

How do you fix Java's ConcurrentModificationException? There are two basic approaches: Do not make any changes to a collection while an Iterator loops through it. If you can't stop the underlying collection from being modified during iteration, create a clone of the target data structure and iterate through the clone.

Does HashMap throw ConcurrentModificationException?

Since Iterator of HashMap is fail-fast it will throw ConcurrentModificationException if you try to remove entry using Map.

How can we avoid ConcurrentModificationException in a single threaded environment?

We can also avoid the Concurrent Modification Exception in a single threaded environment. We can use the remove() method of Iterator to remove the object from the underlying collection object. But in this case, you can remove only the same object and not any other object from the list.


2 Answers

@Eran already explained how to solve this problem better. I will explain why ConcurrentModificationException occurs.

The ConcurrentModificationException occurs because you are modifying the stream source. Your Map is likely to be HashMap or TreeMap or other non-concurrent map. Let's assume it's a HashMap. Every stream is backed by Spliterator. If spliterator has no IMMUTABLE and CONCURRENT characteristics, then, as documentation says:

After binding a Spliterator should, on a best-effort basis, throw ConcurrentModificationException if structural interference is detected. Spliterators that do this are called fail-fast.

So the HashMap.keySet().spliterator() is not IMMUTABLE (because this Set can be modified) and not CONCURRENT (concurrent updates are unsafe for HashMap). So it just detects the concurrent changes and throws a ConcurrentModificationException as spliterator documentation prescribes.

Also it worth citing the HashMap documentation:

The iterators 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.

Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.

While it says about iterators only, I believe it's the same for spliterators.

like image 129
Tagir Valeev Avatar answered Sep 24 '22 08:09

Tagir Valeev


You don't need the Stream API for that. Use retainAll on the keySet. Any changes on the Set returned by keySet() are reflected in the original Map.

someMap.keySet().retainAll(someList); 
like image 31
Eran Avatar answered Sep 25 '22 08:09

Eran