I have a program that is single-threaded that uses a Map where items are removed one by one while iterating. I have read that iterator can be used here to avoid ConcurrentModificationException
but why not use ConcurrentHashMap instead which seems to be much cleaner?
My code:
private final Map<Integer, Row> rowMap;
.....
private void shutDown() {
for (Integer rowNumber : rowMap.keySet()) {
closeRow(rowNumber)
deleteRow(rowNumber)
}
}
....
And in closeRow() method:
private void closeRow(Integer rowNumber) {
Row row = rowMap.remove(rowNumber);
if (row != null) {
row();
}
}
And similar for the deleteRow() method too.
For my scenario, using a iterator means declaring it final so closeRow() and deleteRow() methods have access to it for removing it. Additionally, the iterator.remove() method does not return the value of the item being removed which is necessary in my case.
My question is, what is the most efficient way to do it so it doesn't throw ConcurrentModificationException? Is it using an iterator or making rowMap a ConcurrentHashMap?
Use ConcurrentHashMap
only if it's shared among threads.
In single thread, CurrentModificationException
is thrown when the object is modified while an iterator is being used.
There are two ways to remove elements from a collection such as list and map. One is by calling remove on the collection. The other is using an iterator. But they can't be used together. If you remove an element using the remove method of the collection object, it would invalidate the state of the iterator.
List<Integer> list = new ArrayList(List.of(1,2,3,4,5));
Iterator<Integer> it = list.iterator();
list.remove(0);
while(it.hasNext()){
System.out.print(it.next());
}
Here's the exception:
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
at Main.main(Main.java:15)
It's a fairly straightforward iterator pattern.
Iterator<Map.Entry<Integer,Row>> it = rowMap.entrySet();
while (it.hasNext()) {
Map.Entry<Integer,Row> ent = it.next();
it.remove();
// ... do what you want with ent.getKey() and ent.getValue();
}
You can. In fact, the Map
or ConcurentMap
don't provide an iterator like the List
interface. But you can use the iterator from map.entrySet().iterator();
If you do this you will get a concurrentModifcation exception.
Map<Integer,Integer> map = new HashMap<>(Map.of(1,2,2,2,3,2,4,2,5,3,6,3,7,3));
for(int key : map.keySet()) {
map.put(key*10,4);
}
But here you won't
Map<Integer,Integer> map = new ConcurrentHashMap<>(Map.of(1,2,2,2,3,2,4,2,5,3,6,3,7,3));
for(int key : map2.keySet()) {
map2.put(key*10,4);
}
You can remove the keys in the same way. Note that keySet()
returns a view of the keys so you see some (but perhaps not all) of the keys you just added. I believe this process is non-determinant since it depends on the hash of the keys.
And here is the iterator way.
Iterator<Entry<Integer,Integer>> iter= map.entrySet().iterator();
while(iter.hasNext()) {
iter.next();
iter.remove();
}
System.out.println(map); // prints {}
ConcurrentHashMaps
are primarily for use in multiple thread environments but you can use them to avoid concurrent modification exceptions to.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.