记一次偶然的java.util.ConcurrentModificationException异常

ArrayList集合迭代器删除报错:java.util.ConcurrentModificationException

    public static void main(String[] args) {
List<String> arrayList = new ArrayList<>(); arrayList.add("1"); arrayList.add("2"); arrayList.add("3"); arrayList.add("4"); new Thread(()-> { try { Iterator<String> iterator = arrayList.iterator(); while (iterator.hasNext()) { String curString = iterator.next();
if("2".equals(curString)){ iterator.remove(); //java.util.ConcurrentModificationException } } System.out.println(Thread.currentThread() + ":" + arrayList); } catch (Exception e) { e.printStackTrace(); } }).start(); Iterator<String> iterator = arrayList.iterator(); while (iterator.hasNext()) { String curString = iterator.next(); if("2".equals(curString)){ iterator.remove(); } } System.out.println(arrayList);

运行结果可能报错:

java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.remove(ArrayList.java:865)

查看源码:java.util.ArrayList.Itr,定位报错代码。865行调用checkForComodification()检查并发修改。

再进入checkForComodifacation(),901行 throw new ConcurrentModificationException();

找到原因:当modCount != expectedModCount 时,就会报ConcurrentModificationException异常。

modCount 是抽象类AbstractList的属性,ArrayList继承AbstractList,ArrayList的add和remove方法会使modCount++。

expectedModCount是迭代器Itr类的属性,初始值等于modCount ,用迭代器删除元素后会使expectedModCount=modCount 。

 所以,多个线程用迭代器方式遍历同一个集合删除元素时,modCount是共享变量,当一个线程删除元素后modCount++了,导致另一个线程迭代器Itr的expectedModCount比modCount小,调用checkForComodification()时,modCount != expectedModCount ,就会报ConcurrentModificationException异常。

解决方案:

1. 加锁,用反向for循环遍历来删除元素

  public static void main(String[] args) {
            List<String> arrayList = new ArrayList<>();
            arrayList.add("1");
            arrayList.add("2");
            arrayList.add("3");
            arrayList.add("4");
            ReentrantLock reentrantLock = new ReentrantLock();//可重入锁
            new Thread(()-> {
                try {
                    reentrantLock.lock();
                 for (int i = arrayList.size() -1; i >= 0; i--) {
                      String curString = arrayList.get(i);
                      if("2".equals(curString)){
                          arrayList.remove(i);
                     }
                  }
                  System.out.println(Thread.currentThread() + ":" + arrayList);
                 reentrantLock.unlock();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
       //mian线程 reentrantLock.lock();
for (int i = arrayList.size() -1; i >= 0; i--) { String curString = arrayList.get(i); if("2".equals(curString)){ Thread.sleep(1000l); arrayList.remove(i); } } reentrantLock.unlock(); System.out.println(arrayList); }

运行结果:由于有锁,所以不会删错元素

 

2. 使用CopyOnWriteArrayList类代替ArrayList,注意:CopyOnWriteArrayList不支持迭代器删除。

public static void main(String[] args) throws InterruptedException {
            List<String> arrayList = new ArrayList<>();
            arrayList.add("1");
            arrayList.add("2");
            arrayList.add("3");
            arrayList.add("4");
            CopyOnWriteArrayList<String> copyOnWriteArrayList = new CopyOnWriteArrayList<>(arrayList);
            new Thread(()-> {
                try {
                    for(String string : copyOnWriteArrayList){
                        if("2".equals(string)){
                            copyOnWriteArrayList.remove(string);
                        }
                    }
                    System.out.println(Thread.currentThread() + ":" + copyOnWriteArrayList);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
            //mian线程
            for(String string : copyOnWriteArrayList){
                if("2".equals(string)){
                    copyOnWriteArrayList.remove(string);
                }
            }
            System.out.println(copyOnWriteArrayList);
}

运行结果:CopyOnWriteArrayList写操作都加了ReentrantLock锁,写的时候会新增一个数组,用复制数组的方式写,读写分离。

CopyOnWriteArrayList不保证数据实时一致性,只保证最终一致性。适合读多写少的场景,因为写的时候内存会占用两个数组。

posted @ 2019-02-28 16:56  刘勇1993  阅读(402)  评论(0编辑  收藏  举报