Java Concurrent Modification Error HashMap - java

Fun question here. Why does the first version of this throw a concurrent modification error, while the second does not. Should this happen?
Map<String,Integer> map = new HashMap<>();
... // Populate the map
for(String key : map.keySet()){
if(map.get(key) < 50){
map.remove(key);
}
}
Map<String,Integer> map = new HashMap<>();
... // Populate the map
for(String key : new ArrayList<String>(map.keySet())){
if(map.get(key) < 50){
map.remove(key);
}
}

The first example throws an exception because you modify the map while you are iterating over it. That is expected.
In the second example, you create an ArrayList containing all the strings in the map. Here you iterate over that newly created ArrayList, so your second example don't throw an example because you iterate over the ArrayList, not over the map

This
for(String key : map.keySet()){
if(map.get(key) < 50){
map.remove(key);
}
}
will always throw a ConcurrentModificationException because you remove items while you iterate. What you need is an Iterator which has the remove operation:
for(Iterator<Map.Entry<String, Integer>> it = map.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, Integer> entry = it.next();
if(entry.getValue() < 50) {
it.remove();
}
}
There is also the ConcurrentHashMap which supports concurrent operations and only locks buckets if you need it.

In the first case you get a concurrentModificationException because there's a modification count associated with iteration in terms of internal implementation. If the modification count changes during iteration a concurrentModificaitonException is thrown.
The solution is to use iterator.remove() instead of removing an element directly from the map.
In the second case, you are iterating not the map but a different collection while removing an element from the map. In this case, the modification count never changes during iteration because you are iterating a different collection.
Also, in a multithreaded environment always use synchronized on a collection that represents shared mutable state in a class before iterating it otherwise you can get a concurrentModificationException .
In a multithreaded environment, your second solution would not be correct because you haven't synchronized the statement where you transfer the keyset of the original map to a new collection. So there's potential to get a concurrentModificationException. Use a ConcurrentHashMap in a multithreaded environment while knowing that not every operation or set of operations on a ConcurrentHashMap is threadsafe by default.

Related

How to remove elements from a HashMap without getting ConcurrentModificationException [duplicate]

This question already has answers here:
Iterating through a Collection, avoiding ConcurrentModificationException when removing objects in a loop
(31 answers)
Closed 2 years ago.
I am comparing every entry in a HashMap to every other entry in the same HashMap. While iterating the HashMap I remove some elements based on some conditions. However I keep getting the ConcurrentModificationException.
Iterator<Entry<String, String>> i = map.entrySet().iterator();
while (i.hasNext()) {
Entry<String, String> next = i.next();
for (Entry<String,String> e : map.entrySet()) {
if (e.getKey() != next.getKey()){
String[] positions_e = fields_e[1].split("-");
int start_e = Integer.parseInt(positions_e[0]);
int end_e = Integer.parseInt(positions_e[1]);
String[] positions_next = fields_next[1].split("-");
int start_next = Integer.parseInt(positions_next[0]);
int end_next = Integer.parseInt(positions_next[1]);
if (start_e <= start_next || end_e <= end_next )) {
i.remove();
}
}
}
Use iterator or lambda with removeIf for the second loop.
for (Entry<String,String> e : map.entrySet())
Iterator for map: https://www.techiedelight.com/iterate-map-in-java-using-entryset/
Lambda for map: https://javarevisited.blogspot.com/2017/08/how-to-remove-key-value-pairs-from-hashmap-java8-example.html#axzz5eHbDQxwp
HereĀ“s a method that does not involve a second map or list and also increases the readability of your code:
Extract your condition in a spearate method:
private boolean myCondition(Entry<String, String> currentEntry, Map<String, String> map) {
for (Entry<String, String> entry : map.entrySet()) {
...
if (...) {
return true;
}
return false;
}
}
Use java8 streams to filter the map according to your condition:
Map<String, String> filteredMap = map.entrySet().stream()
.filter(entry -> myCondition(entry, map))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
For the safety of your code, java does not allow you to remove elements that belong to your data structure while you are iterating it. A way of doing it so is: cloning your hashmap, iterating through the copy map and doing the comparison on it. if the condition shows that an element must be removed, try to remove it from your original hash map.
You're removing elements from the collection via the iterator i while iterating over it with a different iterator, the implied one used by your foreach loop.
You're always going to have this problem if you have nested iterations over the same collection and try to remove items from within the inner loop. Using either the inner or outer iterator to remove the element from the collection produces a ConcurrentModificationException from the other iterator.
Since you're using i to remove the element, the best strategy here is probably to break out of the inner loop after calling remove.

Can I use keySet to modify HashMap during iteration?

I know that collections shouldn't be modified during iteration. So we should have workaround.
I have a code:
Map<Key, Value> map = getMap(); // map generating is hidden
for (Key key : map.keySet()) {
if (isToRemove(key)) {
map.remove(key);
} else {
map.put(key, getNewValue());
}
}
Is it undefined behavior or valid code?
keySet documentation sais that changes of the map are reflected in returned set and vice-versa. Does it mean that previous code is unacceptable?
The answer from davidxxx is correct (+1) in pointing out that the view collections on the Map are linked to the map, and that modifications to the map while iterating a view collection may result in ConcurrentModificationException. The view collections on a map are provided by the entrySet, keySet, and values methods.
Thus, the original code:
Map<Key, Value> map = getMap();
for (Key key : map.keySet()) {
if (isToRemove(key)) {
map.remove(key);
} else {
map.add(key, getNewValue());
}
}
will most likely throw ConcurrentModificationException because it modifies the map during each iteration.
It's possible to remove entries from the map while iterating a view collection, if that view collection's iterator supports the remove operation. The iterators for HashMap's view collections do support this. It is also possible to set the value of a particular map entry (key-value pair) by using the setValue method of a Map.Entry instance obtained while iterating a map's entrySet. Thus, it's possible to do what you want to do within a single iteration, without using a temporary map. Here's the code to do that:
Map<Key, Value> map = getMap();
for (var entryIterator = map.entrySet().iterator(); entryIterator.hasNext(); ) {
var entry = entryIterator.next();
if (isToRemove(entry.getKey())) {
entryIterator.remove();
} else {
entry.setValue(getNewValue());
}
}
Note the use of Java 10's var construct. If you're not on Java 10, you have to write out the type declarations explicitly:
Map<Key, Value> map = getMap();
for (Iterator<Map.Entry<Key, Value>> entryIterator = map.entrySet().iterator(); entryIterator.hasNext(); ) {
Map.Entry<Key, Value> entry = entryIterator.next();
if (isToRemove(entry.getKey())) {
entryIterator.remove();
} else {
entry.setValue(getNewValue());
}
}
Finally, given that this is a moderately complicated map operation, it might be fruitful to use a stream to do the work. Note that this creates a new map instead of modifying an existing map in-place.
import java.util.Map.Entry;
import static java.util.Map.entry; // requires Java 9
Map<Key, Value> result =
getMap().entrySet().stream()
.filter(e -> ! isToRemove(e.getKey()))
.map(e -> entry(e.getKey(), getNewValue()))
.collect(toMap(Entry::getKey, Entry::getValue));
The HashMap.keySet() method states more precisely:
The set is backed by the map, so changes to the map are reflected in
the set, and vice-versa.
It means that the elements returned by keySet() and the keys of the Map refer to the same objects. So of course changing the state of any element of the Set (such as key.setFoo(new Foo());) will be reflected in the Map keys and reversely.
You should be cautious and prevent the map from being modified during the keyset() iteration :
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
You can remove entries of the map as :
The set supports element removal, which removes the corresponding
mapping from the map, via the Iterator.remove, Set.remove, removeAll,
retainAll, and clear operations.
But you cannot add entries in :
It does not support the add or addAll operations.
So in conclusion, during keySet() iterator use Set.remove() or more simply iterate with the Iterator of the keySet and invoke Iterator.remove() to remove elements from the map.
You can add new elements in a temporary Map that you will use after the iteration to populate the original Map.
For example :
Map<Key, Value> map = getMap(); // map generating is hidden
Map<Key, Value> tempMap = new HashMap<>();
for (Iterator<Key> keyIterator = map.keySet().iterator(); keyIterator.hasNext();) {
Key key = keyIterator.next();
if (isToRemove(key)) {
keyIterator.remove();
}
else {
tempMap.put(key, getNewValue());
}
}
map.putAll(tempMap);
Edit :
Note that as you want to modify existing entries of the map, you should use an Map.EntrySet as explained in the Stuart Marks answer.
In other cases, using an intermediary Map or a Stream that creates a new Map is required.
If you run your code you get a ConcurrentModificationException. Here is how you do it instead, using an iterator over the keys set or the equivalent Java8+ functional API:
Map<String, Object> bag = new LinkedHashMap<>();
bag.put("Foo", 1);
bag.put("Bar", "Hooray");
// Throws ConcurrentModificationException
for (Map.Entry<String, Object> e : bag.entrySet()) {
if (e.getKey().equals("Foo")) {
bag.remove(e.getKey());
}
}
// Since Java 8
bag.keySet().removeIf(key -> key.equals("Foo"));
// Until Java 7
Iterator<String> it = bag.keySet().iterator();
while (it.hasNext()) {
if (it.next().equals("Bar")) {
it.remove();
}
}

ConcurrentModification Exception with Map and Hashtable

In my application I have used a Map to store POJO objects. As per the requirement I need to iterate over the keySet of the Map and remove objects which dont need any modification.
Conside the code below:
public void remove(Map<String,User> removeUser){
Set<String> keySet = removeUser.keySey();
User user = null;
for(String key : keySet){
user = (user) removeUser.get(key);
if(!user.isActive()){
removeUser.remove(key);
}
}
}
Here in above code, I am getting ConcurrentModificationException when I try to fetch User object after Object removal.
Can anyone tell me why it's happening?
I have not used multi threading.So not able to understand, from where it generated ConCurrentModification Exception.
Even I tried with HashMap and Hashtable, but the problem still exist.
from where it generated ConCurrentModification Exception.
It came from the line where you are trying to remove the element from your Map while iterating over its KeySet.
if(!user.isActive()){
removeUser.remove(key); // This is the problem
}
You should not do that. If you want to modify your Collection or Map, use an iterator.
See this very nice post - efficient-equivalent-for-removing-elements-while-iterating-the-collection explaining the problems that can come while modifying the collection you iterate upon.
Here's a simple code explaining how you can use it here: -
Map<String, Integer> map = new HashMap<String, Integer>() {
{
put("a", 1);
put("b", 2);
put("c", 3);
}
};
Iterator<String> iterate = map.keySet().iterator();
while (iterate.hasNext()) {
int i = map.get(iterate.next());
if(i > 1) {
iterate.remove();
}
}
System.out.println(map);
OUTPUT: -
{a=1}
If you use ConcurrentHashMap it won't produce a ConcurrentModificationException.
The more general solution is to use the Iterator to do the remove().
public void removeInactiveUsers(Map<String, User> map) {
for (Iterator<User> iter = map.values().iterator(); iter.hasNext(); )
if (!user.isActive())
iter.remove();
}
Note: you don't need the keySet() as you are only interested in the values()
use an iterator to iterate over your Set and use iterator.remove(), you cant remove elements from your collection while iterating over it.you'd get a ConcurrentModification Exception
root cause of your Exception is here:
removeUser.remove(key);
iterate over your set like this, using an iterator.
Iterator<String> itr = keySet .iterator();
while(itr.hasNext){
String s = itr.next();
itr.remove(); // to remove the current element.
}

How to delete another item(not the one you are holding) while Iterating the HashMap

I've tried to search over the internet to find a solution of deleting another item but not the one you are visiting. Unfortunately, there is not a way to do it.
Here is the problem.
Assume I have a hashmap and
the items are <0,10> <1,20> <2,30>
Map<Integer,Integer> map = new HashMap<Integer, Integer>() ;
Iterator<Map.Entry<Integer, Integer> >entries = map.entrySet().iterator();
while (entries.hasNext()) {
Entry<Integer, Integer> entry = entries.next();
int temp = entry.getValue();
if (temp==0){
map.remove(2); //2 is the key of 3th item
}
}
Then the problem occours.
Really appreciate the suggestions.
Do it in two passes:
iterate through the entries, and collect the keys to delete in a Set<Integer>
iterate over the set, and remove all the keys it contains from the map. (Or call map.keySet().removeAll(keysToRemove))
Let me guess, you're getting a ConcurrentModificationException.
That's baked in. The javadocs say it may be thrown if you do what you're doing. You can either follow #JBNizet's or you can restart iterating each time you remove an element. Which you choose will depend upon your specific situation.
A 3rd option is to create a copy of the entry set and iterate over that. This one works best if restarting the iteration is expensive and you need to remove quickly.
Iterator<Map.Entry<Integer, Integer> >entries = new HashSet<Map.Entry<Integer, Integer>>(map.entrySet()).iterator();
Do it in 2 passes, 1st accumulate keys to remove, then perform actual removal:
List<Integer> keysToRemove = ...
while (entries.hasNext()) {
Entry<Integer, Integer> entry = entries.next();
int temp = entry.getValue();
if (temp==0){
keysToRemove.add(2);
}
}
for (Integer key : keysToRemove)
map.remove(key);
You cannot modify a HashMap while iterating through it. Instead, you could for example collect a list of keys to remove while iterating through the map, and then remove the items in the list from the map after you have completed the iterating.

What code is required to continuously loop though a HashMap?

Currently my code is causing intermittent ConcurrentModificationException errors, probably because of the way I am looping through the HashMap:
for (Map.Entry<String, Entity> entry : entities.entrySet()) {
String key = entry.getKey();
Entity item = entry.getValue();
if (item.isDestroyed()){
entities.remove(key);
ViewManager.getInstance().removeItem(key);
//INSTRUCT THE ENTITY TO PERFORM IT'S DESTROYED BEHAVIOR item.Destroyed()
} else {
item.update(1);
ConsoleItem ci = new ConsoleItemImpl(item.getIdentifier(), item.getLocation(), ColorStringConverter.getInstance().StringToColor(item.getSide()), item.getAngle(), item.getShape(), item.toString(), item.isDestroyed(), item.isDamaged());
ViewManager.getInstance().updateItem(ci);
}
item.update(1);
}
// updateInfo call
ViewManager.getInstance().updateInfo(summary());
}
How does one continuously loop though a HashMap and avoid a ConcurrentModificationException error?
You cannot modify a map while looping through it. You either have to make a copy of the map, use a ConcurrentHashMap, or use an iterator. If it is in a multi threaded environment, then you can do the modification in a synchronized block.
Another option is to use an iterator.
I rewrote your for for loop with an iterator below:
for(Iterator<Map.Entry<String, Entity>> iterator = entities.entrySet().iterator(); iterator.hasNext(); ){
Map.Entry<String, Entity> entry = iterator.next();
String key = entry.getKey();
Entity item = entry.getValue();
if (item.isDestroyed()){
//Notice using an iterator to remove
iterator.remove();
ViewManager.getInstance().removeItem(key);
//INSTRUCT THE ENTITY TO PERFORM IT'S DESTROYED BEHAVIOR item.Destroyed()
} else {
item.update(1);
ConsoleItem ci = new ConsoleItemImpl(item.getIdentifier(), item.getLocation(), ColorStringConverter.getInstance().StringToColor(item.getSide()), item.getAngle(), item.getShape(), item.toString(), item.isDestroyed(), item.isDamaged());
ViewManager.getInstance().updateItem(ci);
}
item.update(1);
}
I am not sure about the part of continuously looping a HashMap. A hash map has a finite set of keys, so you loop through the key set generally. If you for some reason want to loop continuously then you need to first tell us the reason behind it and also the terminal condition of that continuous loop (it cant really be forever, can it?).
As documented in the other answers, using a Iterator based for-loop, instead of a for-each loop, is your best bet for avoiding ConcurrentModificationExemptions. As for the infinite looping, have a look at the cycle method in Guava's Iterators static utility class. It takes an Iterable (such as a HashMap) and returns an Iterator that continuously loops over the data until either the Iterable is empty or you break the loop.
how about using an iterator and while loop?
Iterator<String> iterator = map.iterator();
String key;
String value;
while (iterator.hasNext()) {
key = iterator.next();
value = map.get(key);
}
You cannot call remote(key) method while looping, but you can remove entries via an iterator:
Map<String, String> map = new HashMap<String, String>();
map.put("one", "1");
map.put("two", "2");
map.put("three", "3");
for (Iterator<Map.Entry<String, String>> i = map.entrySet().iterator(); i.hasNext();)
{
Map.Entry<String, String> curEntry = i.next();
if (curEntry.getKey().equals("two"))
i.remove();
}
Assuming that you are accessing the map in only on thread, use an iterator, as the other guys suggest.
I was thinking about the performance when iterating over a hashmap,
so i did a quick test of 25 iterations over 10^6 random Longs and removing 10% of then,
using different map implementations: ConcurrentHashMap, HashMap, LinkedHashMap
and a TreeMap.
The linked hashmap is supposedly tailored for iterating, and it seems to be the most efficient. I suppose the spikes are due to gc.
...but the differences are smaller than I had anticipated.

Categories