热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

遍历list集合时删除元素出现的异常publicvoidtest(){ArrayListstringsnewArrayList();strings.add(1

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

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

出现错误&#xff1a;
java.util.ConcurrentModificationException
at java.util.ArrayListItr.checkForComodification(ArrayList.java:909)atjava.util.ArrayListItr.checkForComodification(ArrayList.java:909) at java.util.ArrayListItr.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.ParentRunner2.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.IdeaTestRunner2.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.IdeaTestRunner2.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时&#xff0c;如果尝试对集合做一些修改操作&#xff08;例如删除元素&#xff09;&#xff0c;可能会抛出java.util.ConcurrentModificationException的异常。

异常原因
ArrayList的父类AbstarctList中有一个域modCount&#xff0c;每次对集合进行修改&#xff08;增添元素&#xff0c;删除元素……&#xff09;时都会modCount&#43;&#43;

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

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

modCount&#xff1a;表示list集合结构上被修改的次数。&#xff08;在ArrayList所有涉及结构变化的方法中&#xff0c;都增加了modCount的值&#xff09;

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

3&#xff1a;modCount的变量从何而来。
modCount被定义在ArrayList的父类AbstractList中&#xff0c;初值为0&#xff0c;protected transient int modCount &#61; 0。

4&#xff1a;上述代码用来4个方法&#xff0c;操作集合&#xff0c;add(),next(),hasNext(),remove(),这四个方法。
&#xff08;1&#xff09;ArrayList中add()的源代码。

public boolean add(E e) {ensureCapacityInternal(size &#43; 1); // Increments modCount!!elementData[size&#43;&#43;] &#61; e;return true;
}

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

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

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

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

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

在进行文章开头的代码时list集合中使用add添加了元素后&#xff0c;size&#61;2&#xff0c;modCount&#61;2&#xff0c;

&#xff08;2&#xff09;Iterator的方法是返回了一个Itr()的类的一个实例。

public Iterator iterator() { return new Itr(); }

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

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

在for循环内部首先对Iterator进行了初始化&#xff0c;初始化时&#xff0c;expectedModCount&#61;2&#xff0c;cursor&#61;0。

&#xff08;2.1&#xff09;单看hasnext()方法

public boolean hasNext() {return cursor !&#61; size();
}

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

&#xff08;2.2&#xff09;单看next()方法

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

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

final void checkForComodification() {if (modCount !&#61; expectedModCount)throw new ConcurrentModificationException();
}

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

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

&#xff08;3&#xff09;ArrayList中remove的源代码。

public boolean remove(Object o) {if (o &#61;&#61; null) {for (int index &#61; 0; index }

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

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

在fastRemove()方法中&#xff0c;modCount进行了&#43;1操作&#xff0c;modCount&#61;3&#xff0c;size进行了-1的操作&#xff0c;size&#61;1&#xff0c;程序继续进行&#xff0c;进行next()方法&#xff0c;发现modCount不等于expectedModCount&#xff08;expectedModCount&#61;2&#xff09;&#xff0c;抛出了ConcurrentModificationException异常。

解决办法&#xff1a;
1.单线程&#xff1a;

Iterator iter &#61; list.iterator();
while(iter.hasNext()){String str &#61; iter.next();if( str.equals("B") ){iter.remove();}
}

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

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

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

public class Test2 {static List list &#61; new ArrayList();public static void main(String[] args) {list.add("a");list.add("b");list.add("c");list.add("d");new Thread() {public void run() {Iterator iterator &#61; list.iterator();while (iterator.hasNext()) {System.out.println(Thread.currentThread().getName() &#43; ":"&#43; iterator.next());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}};}.start();new Thread() {public synchronized void run() {Iterator iterator &#61; list.iterator();while (iterator.hasNext()) {String element &#61; iterator.next();System.out.println(Thread.currentThread().getName() &#43; ":"&#43; element);if (element.equals("c")) {iterator.remove();}}};}.start();}
}

Output:
在这里插入图片描述

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

此时有两个办法&#xff1a;

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

public class Test2 {static List list &#61; new ArrayList();public static void main(String[] args) {list.add("a");list.add("b");list.add("c");list.add("d");new Thread() {public void run() {Iterator iterator &#61; list.iterator();synchronized (list) {while (iterator.hasNext()) {System.out.println(Thread.currentThread().getName()&#43; ":" &#43; iterator.next());try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}};}.start();new Thread() {public synchronized void run() {Iterator iterator &#61; list.iterator();synchronized (list) {while (iterator.hasNext()) {String element &#61; iterator.next();System.out.println(Thread.currentThread().getName()&#43; ":" &#43; element);if (element.equals("c")) {iterator.remove();}}}};}.start();}
}

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

public class Test2 {static List list &#61; new CopyOnWriteArrayList();public static void main(String[] args) {list.add("a");list.add("b");list.add("c");list.add("d");new Thread() {public void run() {Iterator iterator &#61; list.iterator();while (iterator.hasNext()) {System.out.println(Thread.currentThread().getName()&#43; ":" &#43; iterator.next());try {Thread.sleep(1000);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}};}.start();new Thread() {public synchronized void run() {Iterator iterator &#61; list.iterator();while (iterator.hasNext()) {String element &#61; iterator.next();System.out.println(Thread.currentThread().getName()&#43; ":" &#43; element);if (element.equals("c")) {list.remove(element);}}};}.start();}
}

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


推荐阅读
author-avatar
书友67997456_296
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有