天天看點

一次 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的修改操作,可以試着将修改操作加鎖。