美文网首页
记一次ConcurrentModificationExcepti

记一次ConcurrentModificationExcepti

作者: sollian | 来源:发表于2019-01-09 10:29 被阅读12次

    java相关的工程师,对ConcurrentModificationException应该是很熟悉了。在并发中使用集合,经常一不小心就会碰到这个问题,大多数情况下,加上一些锁就能解决这个bug。而我今天碰到的,就是诡异的加锁之后发生的ConcurrentModificationException

    示例

         class Cache {
            private final List<Person> persons = new ArrayList<>();
    
            List<Person> getPersons() {
                if (persons.isEmpty()) {
                    synchronized (this) {
                        if (persons.isEmpty()) {
                            for (int i = 0; i < 7; i++) {
                               // try {
                               //     Thread.sleep(1);
                               //} catch (InterruptedException e) {
                               //    e.printStackTrace();
                               //}
                                persons.add(new Person());
                            }
                        }
                    }
                }
                return Collections.unmodifiableList(persons);
            }
        }
    

    getPersons方法会在不同的线程调用。为了提高代码效率,特意模仿double-check的单例模式写了persons集合初始化的过程。结果线上就发生了一例ConcurrentModificationException异常。

    百思不得其解。

    原因

    只能说这种写法初衷是好的,但东施效颦了。集合的初始化跟单例的初始化是有很大区别的。将注释的代码解注释,两个线程跑一下,很容易出错。

    原因就在于在A线程执行for循环体时,B线程执行外层persons.isEmpty会返回false,导致B线程最终会拿到一个正在被A线程执行add操作的集合(Collections.unmodifiableList只是对传入的集合做了包装),这样如果B执行集合的遍历操作,就会发生ConcurrentModificationException异常。

    需要注意的是,以下代码也是不可以的:

    static class Cache {
            private List<Person> persons;
    
            List<Person> getPersons() {
                if (persons == null) {
                    synchronized (this) {
                        if (persons == null) {
                            persons = new ArrayList<>();
                            for (int i = 0; i < 7; i++) {
                                try {
                                    Thread.sleep(1);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                                persons.add(new Person());
                            }
                        }
                    }
                }
                return Collections.unmodifiableList(persons);
            }
        }
    

    依然有可能发生ConcurrentModificationException异常。

    解决方式也简单,去掉外层的if逻辑就好了。

    结语

    可能是自己写的代码的缘故,找原因时一直没抓住关键点,最后都要直接try...catch...一下了。还好求助了同事,总算是弄明白了。

    对于Java的非受检异常,还是应该好好思考找到原因,不仅仅使代码更加健壮,也会学到很多东西。try...catch...一时爽,也会断绝成长之路。有时候当局者迷,要学会放低姿态,向别人求助,三人行必有我师,古人诚不欺我也。

    相关文章

      网友评论

          本文标题:记一次ConcurrentModificationExcepti

          本文链接:https://www.haomeiwen.com/subject/oprwrqtx.html