简体   繁体   中英

Java: What is the advantage of using HashMap iterator over ConcurrentHashMap?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM