一次 java.util.ConcurrentModificationException 问题的fix

其他写的比较好的文章:https://www.cnblogs.com/snowater/p/8024776.html

我在一次多线程读写map的时候,然后再遍历的时候也遇到了该问题。

现场代码

private ConcurrentHashMap<Long, Set<Long>> m = new ConcurrentHashMap<>();

// 多线程运行中
public void test(Long p, Long value) {
    Set<Long> s = new HashSet<>();
    if (m.contains(p)) {
        s = m.get(p);
        s.add(value);
    } else {
        s.add(value);
    }
    m.put(p, s);
    for (Long id: s) {
        logger.info("" + id);
    }
}

可以看到,我是在多线程的读写一个线程安全的Map,但我用一个Set去读的map,这个Set可不是线程安全的,再之后的遍历Set的时候,就报了 java.util.ConcurrentModificationException 的错。

分析

我们先看看这个错误是什么,下面是源码

 /**
 * 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;

final class KeySet extends AbstractSet<K> {
    public final int size()                 { return size; }
    public final void clear()               { HashMap.this.clear(); }
    public final Iterator<K> iterator()     { return new KeyIterator(); }
    public final boolean contains(Object o) { return containsKey(o); }
    public final boolean remove(Object key) {
        return removeNode(hash(key), key, null, false, true) != null;
    }
    public final Spliterator<K> spliterator() {
        return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0);
    }
    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 (int i = 0; i < tab.length; ++i) {
                for (Node<K,V> e = tab[i]; e != null; e = e.next)
                    action.accept(e.key);
            }
            if (modCount != mc)
                throw new ConcurrentModificationException();
        }
    }
}

这个是源代码,我们可以看到里面有个modCount来表示修改次数,每次对HashMap的操作都会增加modCount,如果在遍历的时候,发现当前的modCount和遍历的modCount不一致的时候,就会报错。

在我遇到的场景,Set的底层实际上就是用的Map的Key来做实现的,我的Set并不是一个线程安全的,而且还是一个浅拷贝(指向同一个地址),所以在多线程遍历Set的时候,会出现modCount不一致的问题,导致报错。

解决办法

因为避免不了浅拷贝,所以我的解决办法是将set替换成线程安全的,例如 ConcurrentHashMap,也可以是线程安全的Collection。

其他情况的解决办法

将报错的容器替换成线程安全的,例如万能的 ConcurrentHashMap;关注任何map的修改操作,可以试着将修改操作加锁。

posted @ 2020-11-26 20:44  qscqesze  阅读(208)  评论(0编辑  收藏  举报