HashMap之HashMap的快速失败机制(fast-fail)

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值