一、问题引入
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 1st
treeifyBin(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 key
V 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;
else
p.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 return
Node<K,V> current; // current entry
int expectedModCount; // for fast-fail
int index; // current slot
HashIterator() {
expectedModCount = modCount;
Node<K,V>[] t = table;
current = next = null;
index = 0;
if (t != null && size > 0) { // advance to first entry
do {} 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 HashIterator
implements Iterator<K> {
public final K next() { return nextNode().key; }
}
final class ValueIterator extends HashIterator
implements Iterator<V> {
public final V next() { return nextNode().value; }
}
final class EntryIterator extends HashIterator
implements 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

被折叠的 条评论
为什么被折叠?



