1.什么是hashmap的快速失败机制:
在使用迭代器对集合对象进行遍历的时候,如果 A 线程正在对集合进行遍历,此时 B 线程对集合进行修改(增加、删除、修改),或者 A 线程在遍历过程中对集合进行修改,都会导致 A 线程抛出 ConcurrentModificationException 异常。
2.分析:
(1)有如下一段代码:
public static void main(String[] args) {
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("1","1");
hashMap.put("2","1");
Set<Map.Entry<String, String>> entries = hashMap.entrySet();
Iterator<Map.Entry<String, String>> iterator = entries.iterator();
while (iterator.hasNext()){
String key = iterator.next().getKey();
if (key.equals("1")){
hashMap.remove(key);
}
}
}
}
运行后抛出异常:
(2)我们先通过调用hashmap的entrySet()方法,创建了一个EntrySet集合对象,该类是HashMap
的一个内部类
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es;
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
}
(3)然后调用集合对象的iterator()方法创建一个EntryIterator迭代器对象,
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
public final Iterator<Map.Entry<K,V>> iterator() {
return new EntryIterator();
}
.......
}
(4)EntryIterator类也是HashMap的一个内部类
final class EntryIterator extends HashIterator
implements Iterator<Map.Entry<K,V>> {
public final Map.Entry<K,V> next() { return nextNode(); }
}
(5)当我们调用EntryIterator对象的next()方法时,又调用了HashMap的nextNode()方法:
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;
}
(6)在nextNode()方法中对modCount和expectedModCount的值进行了一个比较,如果不相等则会抛出ConcurrentModificationException异常,那么modCount和expectedModCount又代表什么含义呢?
(7)modCount在每次进行put节点和remove节点时,都会进行++modCount操作,实际上他表示的就是对hashmap集合进行修改的次数(CAS中aba问题,有点类似于乐观锁,加上一个版本号,来记录修改)
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
......(此处不是主要分析代码省略)
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
final Node<K,V> removeNode(int hash, Object key, Object value,
boolean matchValue, boolean movable) {
......(此处不是主要分析代码省略)
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;
}
(8)expectedModCount是指期望的修改次数,是HashIterator类中的方法,这个类是EntryIterator类的父类,可以看到在HashIterator的构造方法中对expectedModCount进行了赋值,expectedModCount = modCount;也就是当我们调用EntrySet集合对象的iterator()方法创建迭代器对象时,expectedModCount就已经被赋好了初始值
final class EntryIterator extends HashIterator{}
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;
K key = p.key;
removeNode(hash(key), key, null, false, false);
expectedModCount = modCount;
}
}
(8)此时若我们调用hashmap的remove()方法,移出节点时,modCount执行了++操作,此时若迭代器中还有元素需要遍历,再次调用next()方法时,会发现modCount != expectedModCount,于是抛出了异常
3.注意:
如果当达到迭代器中的最后一个元素时,执行修改或者移出操作,即使modCount被修改了,但是不会在调用next()方法,也就不会再进行判断了,就不会抛出异常。
4.解决办法:
调用迭代器中的remove()方法,因为HashIterator中的remove方法,在removeNode(hash(key), key, null, false, false)移除节点之后,对 expectedModCount进行了重新赋值操作
public class FastFailDemo {
public static void main(String[] args) {
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("1","1");
hashMap.put("2","1");
Set<Map.Entry<String, String>> entries = hashMap.entrySet();
Iterator<Map.Entry<String, String>> iterator = entries.iterator();
while (iterator.hasNext()){
String key = iterator.next().getKey();
if (key.equals("1")){
// hashMap.remove(key);
iterator.remove();
}
}
}
}
5.总结:
在HashMap中,有一个变量modCount来指示集合被修改的次数。在创建Iterator迭代器的时候,会给这个变量赋值给expectedModCount。当集合方法修改集合元素时,例如集合的remove()方法时,此时会修改modCount值,但不会同步修改expectedModCount值。当使用迭代器遍历元素操作时,会首先对比expectedModCount与modCount是否相等。如果不相等,则马上抛出java.util.ConcurrentModificationException异常。而通过Iterator的remove()方法移除元素时,会同时更新expectedModCount的值,将modCount的值重新赋值给expectedModCount,这样下一次遍历时,就不会发抛出ava.util.ConcurrentModificationException异常。
如有问题欢迎指正
原文链接:HashMap阅读笔记 也来说说快速失败机制_我只是无聊-CSDN博客_hashmap快速失败机制
原文链接:HashMap之快速失败_chewbee的专栏-CSDN博客_hashmap快速失败机制
视频链接:HashMap和ConcurrentHashMap深入解析源码合集,看完吊打面试官!|图灵周瑜_哔哩哔哩_bilibili