最近公司要进行代码重构规范,看阿里巴巴java开发手册的时候发现一个有趣的问题
List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); for(String item:list){ if("1".equals(item)){ list.remove(item); } }
当删除1的时候程序就不会报错,但是当删除2的时候就会抛出Exception in thread "main" java.util.ConcurrentModificationException,手册里要求使用的是iterator.remove()的方式,于是顺便撸一下ArrayList的源码(jdk8)。根据堆栈信息
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at com.ywx.springcloud2.Springcloud2Application.main(Springcloud2Application.java:25)
报错来自ArrayList内部的Itr类的next()方法,java的for(Itrm i:list)循环底层是用iterator的hasNext和next实现,Itr是ArrayListiterator的实现(直接上代码)
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() { //next方法时会检查原list 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]; }
final void checkForComodification() { //这里比较了expectedModCount和expectedModCount这两个参数 if (modCount != expectedModCount) throw new ConcurrentModificationException(); }
可以看到在expectedModCount初始化的时候是等于modCount的,然后next方法调用时每次都会检查下这两个值是否相等,(这里相当于一个乐观锁的结构)。
然后查看下这两个值都是在哪定义、改变的,expectedModCount定义位置上面已经标红,modCount 定义在ArrayList 601行,上面还有一大堆注释,表示的是这个list的size改变的次数,在list remove,add等size改变的操作中都会modCount++
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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;
}
那么问题来了,同样是list.remove操作,同样改变了size,应该都报错,为什么1不报错,2报错
关键在于上面代码中标蓝色的位置,当调用next方法获取倒数第二个元素后,cusor到了最后1位大小为原始size-1,然后调用list的remove删除倒数第二个元素,size-1,下次调用hasNext时刚好返回false,所以不会调用next方法,而删除倒数第二个之前的元素时cusor<size-1删除最后一个元素cusor=size,hasNext都返回true,都会调用next方法,导致报错。
对于iterator中的remove()删除后会使expectedModCount = modCount,所以在for(Item item:list)循环中不要用remove add等会改变size的操作。