Java 开发 - HashMap 遍历元素的同时删除元素抛出 ConcurrentModificationException 异常(原理分析、解决方案)
一、问题引入
Map<String, String> map = new HashMap<>();map.put("test1", "this is test1");
map.put("demo2", "this is demo2");
map.put("test3", "this is test3");
map.put("test4", "this is test4");map.forEach((key, value) -> {System.out.println(key + " - " + value);
});System.out.println("------------------------------");map.forEach((key, value) -> {if (key.startsWith("test")) {map.remove(key);}
});map.forEach((key, value) -> {System.out.println(key + " - " + value);
});
# 输出结果test4 - this is test4
test3 - this is test3
test1 - this is test1
demo2 - this is demo2
------------------------------
Exception in thread "main" java.util.ConcurrentModificationException
- 在 Java 开发中,使用 HashMap 遍历元素的同时删除元素,会抛出 ConcurrentModificationException 异常
二、原理分析
- HashMap 内部有一个 modCount,用于记录结构修改次数
public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable {...transient int modCount;...
}
- 当执行 put、remove、clear 操作时,modCount 会增加
public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);else {for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);break;}if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}if (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;
}
public V remove(Object key) {Node<K,V> e;return (e = removeNode(hash(key), key, null, false, true)) == null ?null : e.value;
}final Node<K,V> removeNode(int hash, Object key, Object value,boolean matchValue, boolean movable) {Node<K,V>[] tab; Node<K,V> p; int n, index;if ((tab = table) != null && (n = tab.length) > 0 &&(p = tab[index = (n - 1) & hash]) != null) {Node<K,V> node = null, e; K k; V v;if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))node = p;else if ((e = p.next) != null) {if (p instanceof TreeNode)node = ((TreeNode<K,V>)p).getTreeNode(hash, key);else {do {if (e.hash == hash &&((k = e.key) == key ||(key != null && key.equals(k)))) {node = e;break;}p = e;} while ((e = e.next) != null);}}if (node != null && (!matchValue || (v = node.value) == value ||(value != null && value.equals(v)))) {if (node instanceof TreeNode)((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);else if (node == p)tab[index] = node.next;elsep.next = node.next;++modCount;--size;afterNodeRemoval(node);return node;}}return null;
}
public void clear() {Node<K,V>[] tab;modCount++;if ((tab = table) != null && size > 0) {size = 0;for (int i = 0; i < tab.length; ++i)tab[i] = null;}
}
-
当创建迭代器时,会记录当前的 modCount 作为 expectedModCount
-
在迭代过程中,每次调用 next 方法时都会检查
abstract class HashIterator {Node<K,V> next; // next entry to returnNode<K,V> current; // current entryint expectedModCount; // for fast-failint index; // current slotHashIterator() {expectedModCount = modCount;Node<K,V>[] t = table;current = next = null;index = 0;if (t != null && size > 0) { // advance to first entrydo {} while (index < t.length && (next = t[index++]) == null);}}public final boolean hasNext() {return next != null;}final Node<K,V> nextNode() {Node<K,V>[] t;Node<K,V> e = next;if (modCount != expectedModCount)throw new ConcurrentModificationException();if (e == null)throw new NoSuchElementException();if ((next = (current = e).next) == null && (t = table) != null) {do {} while (index < t.length && (next = t[index++]) == null);}return e;}public final void remove() {Node<K,V> p = current;if (p == null)throw new IllegalStateException();if (modCount != expectedModCount)throw new ConcurrentModificationException();current = null;removeNode(p.hash, p.key, null, false, false);expectedModCount = modCount;}
}
final class KeyIterator extends HashIteratorimplements Iterator<K> {public final K next() { return nextNode().key; }
}final class ValueIterator extends HashIteratorimplements Iterator<V> {public final V next() { return nextNode().value; }
}final class EntryIterator extends HashIteratorimplements Iterator<Map.Entry<K,V>> {public final Map.Entry<K,V> next() { return nextNode(); }
}
三、异常流程分析
-
创建 HashMap,添加 4 个元素,此时,modCount 为 4
-
开始遍历,forEach 内部创建迭代器,执行
expectedModCount = modCount;,此时,modCount 为 4,expectedModCount 为 4 -
处理元素,处理到 test1 时,满足条件,调用 remove 方法,此时,modCount 为 5,expectedModCount 为 4
-
继续处理下一个元素,迭代器调用 next 方法,检查
modCount != expectedModCount,抛出 ConcurrentModificationException 异常
四、解决方案
- 使用 removeIf 方法
Map<String, String> map = new HashMap<>();map.put("test1", "this is test1");
map.put("demo2", "this is demo2");
map.put("test3", "this is test3");
map.put("test4", "this is test4");map.forEach((key, value) -> {System.out.println(key + " - " + value);
});System.out.println("------------------------------");map.entrySet().removeIf(entry -> entry.getKey().startsWith("test"));map.forEach((key, value) -> {System.out.println(key + " - " + value);
});
- 使用迭代器的 remove 方法
Map<String, String> map = new HashMap<>();map.put("test1", "this is test1");
map.put("demo2", "this is demo2");
map.put("test3", "this is test3");
map.put("test4", "this is test4");map.forEach((key, value) -> {System.out.println(key + " - " + value);
});System.out.println("------------------------------");Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {String key = iterator.next();if (key.startsWith("test")) {iterator.remove();}
}map.forEach((key, value) -> {System.out.println(key + " - " + value);
});
- 收集要删除的元素,然后批量删除
Map<String, String> map = new HashMap<>();map.put("test1", "this is test1");
map.put("demo2", "this is demo2");
map.put("test3", "this is test3");
map.put("test4", "this is test4");map.forEach((key, value) -> {System.out.println(key + " - " + value);
});System.out.println("------------------------------");List<String> toRemove = map.keySet().stream().filter(key -> key.startsWith("test")).toList();toRemove.forEach(key -> map.remove(key));map.forEach((key, value) -> {System.out.println(key + " - " + value);
});
五、补充学习
- ConcurrentHashMap 可以使用 forEach 遍历元素的同时删除元素
Map<String, String> map = new ConcurrentHashMap<>();map.put("test1", "this is test1");
map.put("demo2", "this is demo2");
map.put("test3", "this is test3");
map.put("test4", "this is test4");map.forEach((key, value) -> {System.out.println(key + " - " + value);
});System.out.println("------------------------------");map.forEach((key, value) -> {if (key.startsWith("test")) {map.remove(key);}
});map.forEach((key, value) -> {System.out.println(key + " - " + value);
});
# 输出结果test4 - this is test4
test3 - this is test3
test1 - this is test1
demo2 - this is demo2
------------------------------
demo2 - this is demo2
