遍历list集合时删除元素出现的异常

遍历list集合时删除元素出现的异常

 public void test() {
        ArrayList<String> strings = new ArrayList<>();
        strings.add("1");
        strings.add("2");
        for (String item : strings){
            if("2".equals(item)){
                strings.remove(item);
            }
        }
        System.out.println(strings.toString());
    }

出现错误:
java.util.ConcurrentModificationException
at java.util.ArrayList I t r . c h e c k F o r C o m o d i f i c a t i o n ( A r r a y L i s t . j a v a : 909 ) a t j a v a . u t i l . A r r a y L i s t Itr.checkForComodification(ArrayList.java:909) at java.util.ArrayList Itr.checkForComodification(ArrayList.java:909)atjava.util.ArrayListItr.next(ArrayList.java:859)
at test.test(test.java:23)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner 2. e v a l u a t e ( P a r e n t R u n n e r . j a v a : 268 ) a t o r g . j u n i t . r u n n e r s . P a r e n t R u n n e r . r u n ( P a r e n t R u n n e r . j a v a : 363 ) a t o r g . j u n i t . r u n n e r . J U n i t C o r e . r u n ( J U n i t C o r e . j a v a : 137 ) a t c o m . i n t e l l i j . j u n i t 4. J U n i t 4 I d e a T e s t R u n n e r . s t a r t R u n n e r W i t h A r g s ( J U n i t 4 I d e a T e s t R u n n e r . j a v a : 68 ) a t c o m . i n t e l l i j . r t . j u n i t . I d e a T e s t R u n n e r 2.evaluate(ParentRunner.java:268) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.junit.IdeaTestRunner 2.evaluate(ParentRunner.java:268)atorg.junit.runners.ParentRunner.run(ParentRunner.java:363)atorg.junit.runner.JUnitCore.run(JUnitCore.java:137)atcom.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)atcom.intellij.rt.junit.IdeaTestRunnerRepeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)

异常产生
当我们迭代一个ArrayList或者HashMap时,如果尝试对集合做一些修改操作(例如删除元素),可能会抛出java.util.ConcurrentModificationException的异常。

异常原因
ArrayList的父类AbstarctList中有一个域modCount,每次对集合进行修改(增添元素,删除元素……)时都会modCount++

而foreach的背后实现原理其实就是Iterator(关于Iterator可以看Java Design Pattern: Iterator),等同于注释部分代码。在这里,迭代ArrayList的Iterator中有一个变量expectedModCount,该变量会初始化和modCount相等,但如果接下来如果集合进行修改modCount改变,就会造成expectedModCount != modCount,此时就会抛出java.util.ConcurrentModificationException异常

源码解释
1:ConcurrentModificationException异常与modCount这个变量有关。
2:modCount:modCount就是修改次数,在具体的实现类中的Iterator中才会使用。在List集合中,ArrayList是List接口的实现类,

modCount:表示list集合结构上被修改的次数。(在ArrayList所有涉及结构变化的方法中,都增加了modCount的值)

list结构上别修改是指:改变了list的长度的大小或者是遍历结果中产生了不正确的结果的方式。add()和remove()方法会是modCount进行+1操作。modCount被修改后会产生ConcurrentModificationException异常, 这是jdk的快速失败原则

3:modCount的变量从何而来。
modCount被定义在ArrayList的父类AbstractList中,初值为0,protected transient int modCount = 0。

4:上述代码用来4个方法,操作集合,add(),next(),hasNext(),remove(),这四个方法。
(1)ArrayList中add()的源代码。

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

在进行add()方法是,先调用的ensureCapacity()这个方法,来判断是否需要扩容。

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }//看集合是否为空集合。很显然,并不是空集合。
    ensureExplicitCapacity(minCapacity); //
}

在判断是否需要扩容时,用调用ensureExplicitCapacity这个方法,注意,这个方法中对modCount进行的加一操作。

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

所以集合在进行添加元素是会对modCount进行+1的操作。

在进行文章开头的代码时list集合中使用add添加了元素后,size=2,modCount=2,

(2)Iterator的方法是返回了一个Itr()的类的一个实例。

public Iterator<E> iterator() { return new Itr(); }

Itr是ArrayList的一个成员内部类。

private class Itr implements Iterator<E> {
    int cursor;       // cursor:表示下一个要访问的元素的索引
    int lastRet = -1; // 
    int expectedModCount = modCount;
 
    public boolean hasNext() {
        return cursor != size;
    }
 
    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }
 
    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();
        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
        Objects.requireNonNull(consumer);
        final int size = ArrayList.this.size;
        int i = cursor;
        if (i >= size) {
            return;
        }
        final Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length) {
            throw new ConcurrentModificationException();
        }
        while (i != size && modCount == expectedModCount) {
            consumer.accept((E) elementData[i++]);
        }
        // update once at end of iteration to reduce heap write traffic
        cursor = i;
        lastRet = i - 1;
        checkForComodification();
    }
 
    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

在for循环内部首先对Iterator进行了初始化,初始化时,expectedModCount=2,cursor=0。

(2.1)单看hasnext()方法

public boolean hasNext() {
    return cursor != size();
}

hasNext是判断是否右下一个元素,判断条件是cursor不等于size,有下一个元素。size=2,cursor=0,是有下一个元素的。

(2.2)单看next()方法

public E next() {
    checkForComodification();
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
}

当进行next()方法时,调用了checkForComodification()方法,进行判断是否抛出异常

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

此时,modCount=expectedModCount=2,不抛异常,程序继续进行,next()方法有对cursor进行了加一的操作。cursor=1。

item=“1”,继续遍历,hasNext判断是否右下一个元素,有下一个元素。size=2,cursor=1,是有下一个元素的。item=“2”,此时进行remove删除操作。

(3)ArrayList中remove的源代码。

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

可以看见进行remove操作时,是通过调用fastRemove()方法进行的实际删除。在fastRemove()方法中,对modCount进行了+1的操作。

private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; 
}

在fastRemove()方法中,modCount进行了+1操作,modCount=3,size进行了-1的操作,size=1,程序继续进行,进行next()方法,发现modCount不等于expectedModCount(expectedModCount=2),抛出了ConcurrentModificationException异常。

解决办法:
1.单线程:

Iterator<String> iter = list.iterator();
while(iter.hasNext()){
    String str = iter.next();
      if( str.equals("B") )
      {
        iter.remove();
      }
}

Itr中的也有一个remove方法,实质也是调用了ArrayList中的remove,但增加了expectedModCount = modCount;保证了不会抛出java.util.ConcurrentModificationException异常。

但是,这个办法的有两个弊端
1.只能进行remove操作,add、clear等Itr中没有。
2.而且只适用单线程环境。

2.多线程环境
在多线程环境下,我们再次试验下上面的代码

public class Test2 {
    static List<String> list = new ArrayList<String>();

    public static void main(String[] args) {
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");

        new Thread() {
            public void run() {
                Iterator<String> iterator = list.iterator();

                while (iterator.hasNext()) {
                    System.out.println(Thread.currentThread().getName() + ":"
                            + iterator.next());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        }.start();

        new Thread() {
            public synchronized void run() {
                Iterator<String> iterator = list.iterator();

                while (iterator.hasNext()) {
                    String element = iterator.next();
                    System.out.println(Thread.currentThread().getName() + ":"
                            + element);
                    if (element.equals("c")) {
                        iterator.remove();
                    }
                }
            };
        }.start();

    }
}

Output:
在这里插入图片描述

异常的原因很简单,一个线程修改了list的modCount导致另外一个线程迭代时modCount与该迭代器的expectedModCount不相等。

此时有两个办法:

迭代前加锁,解决了多线程问题,但还是不能进行迭代add、clear等操作

public class Test2 {
    static List<String> list = new ArrayList<String>();

    public static void main(String[] args) {
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");

        new Thread() {
            public void run() {
                Iterator<String> iterator = list.iterator();

                synchronized (list) {
                    while (iterator.hasNext()) {
                        System.out.println(Thread.currentThread().getName()
                                + ":" + iterator.next());
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                }
            };
        }.start();

        new Thread() {
            public synchronized void run() {
                Iterator<String> iterator = list.iterator();

                synchronized (list) {
                    while (iterator.hasNext()) {
                        String element = iterator.next();
                        System.out.println(Thread.currentThread().getName()
                                + ":" + element);
                        if (element.equals("c")) {
                            iterator.remove();
                        }
                    }
                }
            };
        }.start();

    }
}

采用CopyOnWriteArrayList,解决了多线程问题,同时可以add、clear等操作

public class Test2 {
    static List<String> list = new CopyOnWriteArrayList<String>();

    public static void main(String[] args) {
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");

        new Thread() {
            public void run() {
                Iterator<String> iterator = list.iterator();

                    while (iterator.hasNext()) {
                        System.out.println(Thread.currentThread().getName()
                                + ":" + iterator.next());
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
            };
        }.start();

        new Thread() {
            public synchronized void run() {
                Iterator<String> iterator = list.iterator();

                    while (iterator.hasNext()) {
                        String element = iterator.next();
                        System.out.println(Thread.currentThread().getName()
                                + ":" + element);
                        if (element.equals("c")) {
                            list.remove(element);
                        }
                    }
            };
        }.start();

    }
}

CopyOnWriteArrayList也是一个线程安全的ArrayList,其实现原理在于,每次add,remove等所有的操作都是重新创建一个新的数组,再把引用指向新的数组。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值