HashMap中modCount属性用实现fail-fast机制,即遍历过程如果有增删改,则马上抛出异常。每次增删改modCount加一。
KeySet和Values中都有forEach方法。
public final void forEach(Consumer<? super K> action) {
Node<K,V>[] tab;
if (action == null)
throw new NullPointerException();
if (size > 0 && (tab = table) != null) {
int mc = modCount;
for (Node<K,V> e : tab) {
for (; e != null; e = e.next)
action.accept(e.key);
}
if (modCount != mc)
throw new ConcurrentModificationException();
}
}
代码可知forEach是全部遍历完才比较,所以这个不算是fail-fast。
KeySet和Values中Iterator<K> iterator() 方法实现Iterator接口。
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;
}
expectedModCount初始化为modCount
nextNode()中会先判断是否变化,变化则直接fail-fast
remove()比较特殊,先判断是否变化,变化则直接fail-fast。然后删除,这时modCount会变化。最后再expectedModCount = modCount;这样避免了fail-fast。
所以如果遍历同时修改则必须调用iterator的remove方法才可以。
如果遍历时直接调用HashMap的remove方法会抛出异常。
forEach虽然也会抛出异常,但是并不是fail-fast。