前言
我在阅读Java.util.ArrayList源码的时候,发现不少方法中有”modCount++;”,很好奇这个modCount有何作用,一番查证,找到了一些有趣的东西
参考:https://blog.youkuaiyun.com/androiddevelop/article/details/21509345
摘要
modCount 记录修改次数,
ConcurrentModficationException 并发修改异常,
通过Iterator遍历过程中,如果对修改ArrayList进行修改,可能会引发”并发修改异常”,
这是操作的不规范,让我带大家跳坑,详细分析这个错误,死而后已。
一、错误示范,First Blood.
public static void main(String[] args){
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(3);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==3)
list.remove(integer); //注意这个地方
}
}
重点来了,这份代码的报错,可不是IndexOutOfBounds越界操作,而是所谓的ConcurrentModficationException并发修改异常,如下图:
让我们撕开她的外衣,看看里面的东西吧
二、 ConcurrentModfication异常分析
前面和数组越界异常进行比较,事实上,这个异常与Iterator迭代器有关,是迭代器出问题时的异常。
对于上面这个例子来说,是在下面这个地方进入了异常。
while(iterator.hasNext()){
Integer integer = iterator.next();
...
}
我们观察ArrayList的hasNext()代码:
public boolean hasNext() {
return cursor != size;
}
list本来有一个元素,删除过后size为0,但此时迭代器游标cursor为1,查看hasNext(),当然不相等。继续循环!!(ps:如果因为删除而cursor==size,会安全退出,但实际并非我们想要的结果)
然后遇见了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];
}
checkForComodification()
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
没错,创建迭代器的时候会将当时的modCount赋值给expectedModCount,靠这个来判断是否遍历时进行了修改操作。
三、 如何解决
// 1 使用Iterator提供的remove方法,用于删除当前元素
for (Iterator<String> it = myList.iterator(); it.hasNext();) {
String value = it.next();
if (value.equals( "3")) {
it.remove(); // ok
}
}
System. out.println( "List Value:" + myList.toString());
// 2 建一个集合,记录需要删除的元素,之后统一删除
List<String> templist = new ArrayList<String>();
for (String value : myList) {
if (value.equals( "3")) {
templist.remove(value);
}
}
// 可以查看removeAll源码,其中使用Iterator进行遍历
myList.removeAll(templist);
System. out.println( "List Value:" + myList.toString());
// 3. 使用线程安全CopyOnWriteArrayList进行删除操作
List<String> myList = new CopyOnWriteArrayList<String>();
myList.add( "1");
myList.add( "2");
myList.add( "3");
myList.add( "4");
myList.add( "5");
Iterator<String> it = myList.iterator();
while (it.hasNext()) {
String value = it.next();
if (value.equals( "3")) {
myList.remove( "4");
myList.add( "6");
myList.add( "7");
}
}
System. out.println( "List Value:" + myList.toString());
// 4. 不使用Iterator进行遍历,需要注意的是自己保证索引正常
for ( int i = 0; i < myList.size(); i++) {
String value = myList.get(i);
System. out.println( "List Value:" + value);
if (value.equals( "3")) {
myList.remove(value); // ok
i--; // 因为位置发生改变,所以必须修改i的位置
}
}
System. out.println( "List Value:" + myList.toString());
在遍历时的同时进行操作,本身就是一个非常危险的行为,这个并发修改异常就是为了基于此的一种保护。所以,我们要做的,不是寻找方法突破这个保护机制,而更应该在程序编写上规避这类行为。