问题案例分析
public class ArrayListExceptionTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
/*for (String s : list) {
if ("2".equals(s)){
list.remove(s);
}
}*/
for (String s : list) {
if ("1".equals(s)){
list.remove(s);
}
}
}
}
结果1:我们发现删除“1”的时候会报一个并发修改的异常
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at ArrayListExceptionTest.EqualUnderstand.main(EqualUnderstand.java:38)
结果2:当我们删除“2”的时候,我们发现可以正常删除,却不报错
当我们更换数组里的大小和内容时,我们发现使用ForEach遍历数组时,只能删除倒数第二个,删除其他则报错!!!这是什么原因呢?
ForEach底层使用迭代器进行遍历的,:它会先指针移动来判断是否当前的数组是否有值,再去获取下一个值
下面是伪代码:
- array:表示遍历的数组
- hasNext():判断下一个位置是否还有值得方法
- next():将指针移动到下一个位置,并且返回当前指针所在位置的值
while(array.haxNext()){
int value = array.next();
}
步骤1:下面我们点击ArrayList的next()源码方法查看
根据next源码发现:
- 我们发现它会有个两个变量
- cursor变量: 来记录索引的位置,
- size:记录当前数组的长度或者大小
- 当
cursor == size
时,表示遍历结束
根据checkForComodification()源码发现:
我们可以知道当移动到下一位时,expectedModCount != ArrayList.this.modCount则抛出并发修改异常
- modCount:删除的次数
- expectedModCount :期待删除的次数
- 在迭代器初始化时,expectedModCount ==ArrayList.this.modCount
public E next() {
checkForComodification(); //检查是否需要抛出并发修改异常
int i = cursor; //cursor索引
if (i >= size) //size为当前数组的长度,如果cusor>size,则抛出没有下一个元素的异常
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData; //获取当前数组
if (i >= elementData.length) //如果cursor>数组.length,则提示并发修改异常
throw new ConcurrentModificationException();
cursor = i + 1; //索引向前移动 cusor+1
return (E) elementData[lastRet = i]; //lastRet:取出当前元素所在的索引
}
/*modCount:表示被删除的次数,expectedModCount:期待被删除的次数
当迭代器初始化的时候expectedModCount == ArrayList.this.modCount,进行删除操作时,modCount+1
*/
final void checkForComodification() {
if (expectedModCount != ArrayList.this.modCount)
throw new ConcurrentModificationException();
}
步骤2:查看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;
}
我们点击查看fastRemove方法
我们可以发现,每次执行remove方法时,会去调用fastRmove方法,会将modCount++,modCount为删除的次数
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; // clear to let GC do its work
}
原理分析
- 所以每次当我们执行删除时,
modCount+1
,导致expectedModCount != ArrayList.this.modCount
,抛出并发修改异常 - 只有当倒数第二个元素的时候,cusor==size时,认为是遍历结束,则执行next()来进行判断是否并发修改
流程图如下:
为什么使用iter迭代器删除,却不会报错呢?
案例:
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
String s = iterator.next();
if ("2".equals(s)){
iterator.remove();
}
}
}
我们发现无论删除什么元素,都是不会报错的!!!!
我们点击remove查看源码:
我发现调用remove方法时,直接赋值修改·expectedModCount = modCount
,这样在进行next()检查时,就不会抛出并发修改异常了!!
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();
}
}