背景
在和朋友的一次交谈中,朋友遇到了这样一个问题:在遍历map时,做了一步remove操作,然后就发生了ConcurrentModificationException异常。由此我点进源码探究了一番。
JDK版本:1.8
探究
点进HashMap源码发现,在每次循环结束前,会校验一下modCount的值,如果modCount变了,就会抛出ConcurrentModificationException异常。
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (int i = 0; i < tab.length; ++i) {
for (Node<K,V> e = tab[i]; e != null; e = e.next)
action.accept(e.key, e.value);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
那么modCount又是什么呢,结合网上搜索出来的以及源码注释的描述,这个参数是用来在HashMap迭代时,可以快速发现错误并结束迭代的。
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
transient int modCount;
知道这个参数的含义后,点击modCount,查看有哪些地方会修改他的值。发现有这些地方会修改modCount。最后赋值为0的方法(reinitialize)是重新初始化HashMap的参数,这个可以略过。
putVal
661行,也是put()实际调用的方法。从源码中我们可以看到,当key存在时,会直接return oldValue结束方法。也就是说只有在插入新的key时会导致modCount增加。
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.equ