场景
有如下代码运行:
@Test
public void testRemove() {
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
for (String item : list) {
// 当为1时运行正常,改成2时异常ConcurrentModificationException
if ("2".equals(item)) {
list.remove(item);
}
}
System.out.println(PrintUtils.parintArrayList(list));
}
异常如下:
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:)
at java.util.ArrayList$Itr.next(ArrayList.java:)
at NormalTest.testRemove(NormalTest.java:)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:)
...
分析
将字节码反编译
上图发现,我们简简单单的一句
for (String item : list) {
在真实的字节码中却变成了红框中的部分,进一步分析。
跟踪源码
第一行
public Iterator<E> iterator() {
// ArrayList中的一个内部类
return new Itr();
}
在创建该对象时有这么一句赋值语句
该字段在ArrayList中标识当前对象的修改次数(包括remove和add方法),跟踪代码不难发现都有这么一行代码
modCount++;
第二行
查看while条件中的源码,并不会发生异常,暂且不管
public boolean hasNext() {
return cursor != size;
}
第三行
仔细查看
Itr
代码中的
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 + ;
return (E) elementData[lastRet = i];
}
再看
checkForComodification
方法,
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
结合上面的判断,在创建Itr对象时就将ArrayList的修改次数
modCount
赋值给Itr迭代器对象,如果在迭代期间ArrayList对象被操作(remove和add)了导致
modCount
值修改,就会报异常
ConcurrentModificationException
。
再分析
那么为什么当判断条件为“1”时
if ("1".equals(item)) {
就不会报错呢?
查看while条件中的源码涉及到两个字段
cursor
和
size
,做个表格来一步一步记录下值的变化
public boolean hasNext() {
return cursor != size;
}
判断条件为 if ("1".equals(item)) {
if ("1".equals(item)) {
注:因为是两次add,所以导致了
expectedModCount
和
modCount
初始值为2
迭代次数 | cursor | size | expectedModCount | modCount |
---|---|---|---|---|
新创建 | 2 | 2 | 2 | |
第一次 | 1 | 2 | 2 | 2 |
执行 操作后 | 1 | 1 | 2 | 3 |
以上就能看到,当
remove
操作后就导致了
cursor
和
size
一致,也就退出了while循环,避免了异常抛出。
判断条件为 if ("2".equals(item)) {
if ("2".equals(item)) {
迭代次数 | cursor | size | expectedModCount | modCount |
---|---|---|---|---|
新创建 | 2 | 2 | 2 | |
第一次 | 1 | 2 | 2 | 2 |
第二次 | 2 | 2 | 2 | 2 |
执行 操作后 | 2 | 1 | 2 | 3 |
判断条件仍为 ,继续执行 | 2 | 1 | 2 | 3 |
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
所以在
next
代码块中的
checkForComodification
就抛出了异常,以上也就完美了解释了为什么当判断条件为2时抛异常了。