background

In a conversation, and friends of friends met such a problem: when traversing map, made a step remove operation, and then ConcurrentModificationException exception occurred. From this I point into the source code to explore some. JDK version: 1.8Copy the code

To explore the

Point into the HashMap source found that before the end of each loop, will check modCount values, if modCount changed, will throw ConcurrentModificationException.Copy the code
@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 newConcurrentModificationException(); }}Copy the code
So what about modCount? In combination with the description from the Internet search and the source code comment, this parameter is used to quickly find errors and end iterations on a HashMap.Copy the code
/** * 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;
Copy the code
Once you know what this parameter means, click modCount to see where it changes its value. I find these places where I'm going to change modCount. The last method assigned to 0 (reinitialize) is the argument to reinitialize the HashMap, which can be skipped.Copy the code

putVal

Line 661, which is also the method put() actually calls. As we can see from the source code, when the key is present, the oldValue terminating method is returned directly. That is, the modCount will increase only if you insert a new key.Copy the code
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;
}
Copy the code

removeNode

Line 845, also the method that remove() actually calls. ModCount is changed when data is deleted.Copy the code
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);
            returnnode; }}return null;
}
Copy the code

clear

Line 860. Reset each node in the map to null. And it changes the modCount value when it's called.Copy the code
public void clear(a) {
    Node<K,V>[] tab;
    modCount++;
    if((tab = table) ! =null && size > 0) {
        size = 0;
        for (int i = 0; i < tab.length; ++i)
            tab[i] = null; }}Copy the code

computeIfAbsent

Line 1142. This method checks if the key exists, and if it does not, adds and modCount++.Copy the code
@Override
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
    if (mappingFunction == null)
        throw new NullPointerException();
    int hash = hash(key);
    Node<K,V>[] tab; Node<K,V> first; int n, i;
    int binCount = 0;
    TreeNode<K,V> t = null;
    Node<K,V> old = null;
    if (size > threshold || (tab = table) == null ||
        (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((first = tab[i = (n - 1) & hash]) ! =null) {
        if (first instanceof TreeNode)
            old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
        else {
            Node<K,V> e = first; K k;
            do {
                if(e.hash == hash && ((k = e.key) == key || (key ! =null && key.equals(k)))) {
                    old = e;
                    break;
                }
                ++binCount;
            } while((e = e.next) ! =null);
        }
        V oldValue;
        if(old ! =null&& (oldValue = old.value) ! =null) {
            afterNodeAccess(old);
            return oldValue;
        }
    }
    V v = mappingFunction.apply(key);
    if (v == null) {
        return null;
    } else if(old ! =null) {
        old.value = v;
        afterNodeAccess(old);
        return v;
    }
    else if(t ! =null)
        t.putTreeVal(this, tab, hash, key, v);
    else {
        tab[i] = newNode(hash, key, v, first);
        if (binCount >= TREEIFY_THRESHOLD - 1)
            treeifyBin(tab, hash);
    }
    ++modCount;
    ++size;
    afterNodeInsertion(true);
    return v;
}
Copy the code

compute

Line 1214. Similar to computeIfAbsen(), it is added and modCount++ only if the key does not exist and the value returned by remappingFunction is not empty.Copy the code
@Override
public V compute(K key,
                 BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
    if (remappingFunction == null)
        throw new NullPointerException();
    int hash = hash(key);
    Node<K,V>[] tab; Node<K,V> first; int n, i;
    int binCount = 0;
    TreeNode<K,V> t = null;
    Node<K,V> old = null;
    if (size > threshold || (tab = table) == null ||
        (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((first = tab[i = (n - 1) & hash]) ! =null) {
        if (first instanceof TreeNode)
            old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
        else {
            Node<K,V> e = first; K k;
            do {
                if(e.hash == hash && ((k = e.key) == key || (key ! =null && key.equals(k)))) {
                    old = e;
                    break;
                }
                ++binCount;
            } while((e = e.next) ! =null);
        }
    }
    V oldValue = (old == null)?null : old.value;
    V v = remappingFunction.apply(key, oldValue);
    if(old ! =null) {
        if(v ! =null) {
            old.value = v;
            afterNodeAccess(old);
        }
        else
            removeNode(hash, key, null.false.true);
    }
    else if(v ! =null) {
        if(t ! =null)
            t.putTreeVal(this, tab, hash, key, v);
        else {
            tab[i] = newNode(hash, key, v, first);
            if (binCount >= TREEIFY_THRESHOLD - 1)
                treeifyBin(tab, hash);
        }
        ++modCount;
        ++size;
        afterNodeInsertion(true);
    }
    return v;
}
Copy the code

merge

Line 1273. If the key does not exist, key-value is added and modCount++ is added.Copy the code
@Override
public V merge(K key, V value,
               BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
    if (value == null)
        throw new NullPointerException();
    if (remappingFunction == null)
        throw new NullPointerException();
    int hash = hash(key);
    Node<K,V>[] tab; Node<K,V> first; int n, i;
    int binCount = 0;
    TreeNode<K,V> t = null;
    Node<K,V> old = null;
    if (size > threshold || (tab = table) == null ||
        (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((first = tab[i = (n - 1) & hash]) ! =null) {
        if (first instanceof TreeNode)
            old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
        else {
            Node<K,V> e = first; K k;
            do {
                if(e.hash == hash && ((k = e.key) == key || (key ! =null && key.equals(k)))) {
                    old = e;
                    break;
                }
                ++binCount;
            } while((e = e.next) ! =null); }}if(old ! =null) {
        V v;
        if(old.value ! =null)
            v = remappingFunction.apply(old.value, value);
        else
            v = value;
        if(v ! =null) {
            old.value = v;
            afterNodeAccess(old);
        }
        else
            removeNode(hash, key, null.false.true);
        return v;
    }
    if(value ! =null) {
        if(t ! =null)
            t.putTreeVal(this, tab, hash, key, value);
        else {
            tab[i] = newNode(hash, key, value, first);
            if (binCount >= TREEIFY_THRESHOLD - 1)
                treeifyBin(tab, hash);
        }
        ++modCount;
        ++size;
        afterNodeInsertion(true);
    }
    return value;
}
Copy the code

conclusion

The reinitialize and clear methods reset the entire HashMap, removeNode deletes the data, and everything else adds data. When these operations are performed, the modCount changes. In traverse HashMap, if the above operation will lead to abnormal ConcurrentModificationException.Copy the code