使用foreach遍历时,不能对集合进行新增和删除
List<String> list = new ArrayList();
list.add("123");
list.add("321");
list.add("456");
list.add("789");
for (String s : list) {
System.out.println(1);
if ("321".equals(s)) {
list.remove(s);
// list.add("xxx");
}
System.out.println(2);
}
-------------打印如下---------------
1
2
1
2
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:907)
at java.util.ArrayList$Itr.next(ArrayList.java:857)
删除元素之后,在遍历下一个元素时,会报错,看一下ArrayList对于迭代器的实现
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
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();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
ArrayList迭代器的next方法里有一个checkForComodification方法,每次调用next方法,都会校验modCount和expectedModCount的值是否一致
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
那么modCount和expectedModCount的值又是从哪来的呢?modCount是集合的一个属性,它记录了当前集合对象被修改的次数,调用集合自身的add、remove等方法都会让modCount的值自增1,expectedModCount是迭代器的一个属性,在创建迭代器时,就会将当前modCount的值赋给expectedModCount,如果某个集合对象,modCount的值为2,在构建完迭代器后,expectedModCount也为2,当使用集合对象自身的remove方法进行删除元素后,modCount会自增1,变为3,但是并未对expectedModCount进行任何操作,所以当迭代器遍历下一个元素时,modCount的值(3)显然和expectedModCount的值(2)是不等的。
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
Java为什么会这么设计呢?
这其实是java集合的一种快速失败机制:fail-fast,快速失败的意思就是一旦检测到有异常,就立马停止工作并上报,而不是试图继续运行可能存在缺陷的系统。ArrayList的快速失败机制就是用modCount来实现的,通过检测modCount和expectedModCount的值是否相等,来表明是否有异常情况发生(产生了并发修改)。这种机制主要被用来设计在多线程并发的场景下,当多个线程同时对集合操作时,就有可能触发fail-fast,这里是可能,而不是一定,因为当线程只改变集合元素属性(只是简单的修改一个已存在元素的值),而不是改变集合结构时(添加、删除元素),是不会触发的。虽然这个机制主要被设计在多线程场景下,但是上面的例子是发生在单线程下的,说明在单线程下,也会触发这个机制,迭代器只知道在遍历的时候,集合结构发生了改变,但是不知道是单线程导致的还是多线程导致的,那么我们如果来避免单线程的这种场景呢?
- 使用迭代器自带的添加、删除方法,看迭代器源码会发现,当用迭代器自带的方法添加/删除完元素后,会将modCount的值再次赋值给expectedModCount
- 使用fail-safe机制的集合类,如CopyOnWriteArrayList,这里就不单独去讲它了,等以后有时间再单独出一篇CopyOnWriteArrayList的实现
– jdk版本:1.8