wangjie-fourth 的个人博客

may be just need work hard

目录
java.util.ConcurrentModificationException异常
/    

java.util.ConcurrentModificationException异常

今天,在写代码的时候,遇到这么一个异常。java.util.ConcurrentModificationException,可重现代码如下:

Map<String, String> map = new HashMap<>();

map.put("a", "a");
map.put("b", "b");
map.put("c", "c");

for (Map.Entry<String, String> entry : map.entrySet()) {
	map.remove("a");
}

发生异常的原因就是,在迭代HashMap的时候,不能去删除其中的元素。

为什么会发生这个异常

1、在HashMap.removeNode方法中

    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 &&
            ....
            if (node != null && (!matchValue || (v = node.value) == value ||
                                 (value != null && value.equals(v)))) {
                ...
                【++modCount;】
                --size;
                afterNodeRemoval(node);
                return node;
            }
        }
        return null;
    }

这里在删除HashMap元素的时候,会让modCount+1

2、在HashMap.HashIterator.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;
        }

迭代器迭代的时候,会验证modCountexpectedModCount是否相等,而expectedModCount初始赋值是3,modCount在删除一个元素后,其值变为2;所以抛了异常。

解决的办法

从网上搜集的方法有:

1、使用ConcurrentHashMap

        Map<String, String> map = new ConcurrentHashMap<>();

        map.put("a", "a");
        map.put("b", "b");
        map.put("c", "c");

        for (Map.Entry<String, String> entry : map.entrySet()) {
            map.remove("a");
        }

ConcurrentHashMap里面没有fast-fail机制的modCountexpectedModCount

2、使用Iteratorremove方法

HashMap的迭代器不允许在迭代的时候,删除元素。但Iterator可以。

        Map<String, String> map = new ConcurrentHashMap<>();

        map.put("a", "a");
        map.put("b", "b");
        map.put("c", "c");
        for (Iterator<Map.Entry<String, String>> it = map.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry<String, String> entry = it.next();
            if (entry.getKey().equals("a")) {
                it.remove();
            }
        }

也可以根据IDEA的提示将代码简化为

        Map<String, String> map = new ConcurrentHashMap<>();

        map.put("a", "a");
        map.put("b", "b");
        map.put("c", "c");
        map.entrySet().removeIf(entry -> entry.getKey().equals("a"));

为什么HashMap要加入fast-fail机制

1、看看HashMapmodCount的源码注释

    /**
     * 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值为这个HashMap修改的次数,也就是每添加、删除元素的时候,就+1

2、再看看ConcurrentModificationException源码上面的注释

/**
 * This exception may be thrown by methods that have detected concurrent
 * modification of an object when such modification is not permissible.
 * <p>
 * For example, it is not generally permissible for one thread to modify a Collection
 * while another thread is iterating over it.  In general, the results of the
 * iteration are undefined under these circumstances.  Some Iterator
 * implementations (including those of all the general purpose collection implementations
 * provided by the JRE) may choose to throw this exception if this behavior is
 * detected.  Iterators that do this are known as <i>fail-fast</i> iterators,
 * as they fail quickly and cleanly, rather that risking arbitrary,
 * non-deterministic behavior at an undetermined time in the future.
 * <p>
 * Note that this exception does not always indicate that an object has
 * been concurrently modified by a <i>different</i> thread.  If a single
 * thread issues a sequence of method invocations that violates the
 * contract of an object, the object may throw this exception.  For
 * example, if a thread modifies a collection directly while it is
 * iterating over the collection with a fail-fast iterator, the iterator
 * will throw this exception.
 *
 * <p>Note that fail-fast behavior cannot be guaranteed as it is, generally
 * speaking, impossible to make any hard guarantees in the presence of
 * unsynchronized concurrent modification.  Fail-fast operations
 * throw {@code ConcurrentModificationException} on a best-effort basis.
 * Therefore, it would be wrong to write a program that depended on this
 * exception for its correctness: <i>{@code ConcurrentModificationException}
 * should be used only to detect bugs.</i>
 */

这里介绍这个异常主要用于检查是否有一个人在迭代集合,另一个人在修改集合结构。如何检查出来呢?就是通过fast-fail机制的modCountexpectedModCount。每次迭代的时候,会检查这俩个是否相等。

其实,这也是为了多线程的情况下,脏数据的问题。只要我们用线程安全的容器,就不存在这个问题了。

疑惑的点

1、在【ConcurrentModificationException】文档中提到,如果在迭代器迭代的时候,去修改元素,可能会造成迭代结果不确定。但Iterator可以在迭代集合的时候,删除元素。所以,为什么使用HashMap的迭代器会出现这个错误?

 * For example, it is not generally permissible for one thread to modify a Collection
 * while another thread is iterating over it.  In general, the results of the
 * iteration are undefined under these circumstances. 

参考信息

https://blog.csdn.net/chenssy/article/details/38151189
https://www.jianshu.com/p/c5b52927a61a

评论