zoukankan      html  css  js  c++  java
  • 一次 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的修改操作,可以试着将修改操作加锁。

  • 相关阅读:
    关于在MyEclipse中页面中文乱码的问题
    如何用Navicat for MySQL 将mysql中的数据库导出,导入。
    淘宝链接池的配置
    c3p0配置
    人生规划
    spring问题: Unable to validate using XSD: Your JAXP provider
    List数组和Set集合
    Tomcat6内存不足问题及解决方法
    清华校长送给毕业生的五句话
    个人图文理解类的封装
  • 原文地址:https://www.cnblogs.com/qscqesze/p/14044452.html
Copyright © 2011-2022 走看看