背景
近日,在看ArrayList的源码实现,发现很多情况会抛出ConcurrentModificationException。下面总结一下大致发生的情况。 首先,ArrayList不是线程安全的。 首先来看一个例子:
public static void main(String[] args){
List<Integer> aList = new ArrayList<Integer>();
aList.add(1);
aList.add(2);
Iterator<Integer> iter = aList.iterator();
aList.add(3);
System.out.println(iter.next());
}
运行结果:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at com.zhu.util.ArrayIistIteratorTest.main(ArrayIistIteratorTest.java:19)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
由例子可见,在调用iterator方法后,如果其他线程对ArrayList进行了更改大小的操作,如add和remove。那么将会抛出ConcurrentModificationException。字面意思很简单:并发修改异常。
这种情况也会出现
List<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
list.add("C");
for (String s : list) {
if (s.equals("B")) {
list.remove(s);
}
}
运行后
分析
我们通过源码,来看看为什么会出现这样的情况: 首先ArrayList的iterator方法实现:
public Iterator<E> iterator() {
return new Itr();
}
Itr是ArrayList的内部类,实现了Iterator接口,下面是Itr的源码:
private class Itr implements Iterator<E> {
/**
* Index of element to be returned by subsequent call to next.
*/
int cursor = 0;
/**
* Index of element returned by most recent call to next or
* previous. Reset to -1 if this element is deleted by a call
* to remove.
*/
int lastRet = -1;
/**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification();
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
Itr只有三个成员变量:cursor,lastRet,exceptdModCount
- cursor:next方法应该返回元素所在的下标
- lastRet:上一次next返回元素的下标
- exceptedModCount:ArrayList成员变量modCount的副本 ArrayList中modCount的意义是记录
当前ArrayList被修改的次数,ArrayList中会引起modCount发生改变的方法有以下集中:
- trimToSize:将数组大小减小至当前元素个数
- ensureCapacity:容量设置
- add:新增元素的时候
- remove:移除元素的时候
- clear:清空的时候 所有移除和新增的操作都会引起modCount的修改
可以看出引起例子抛出异常的原因是因为Itr创建之后,exceptedModCount为当时ArrayList对象modCount的值。在Itr的next和remove方法中可以看出,在世纪操作之前都会调用checkForComodification来检查ArrayList是否被修改过。在调用Itr中next和remove与Itr创建之间,如果有其他线程或本线程调用了引起ArrayList的modCount发生变化的操作,那么将会抛出并发修改异常。
那么下面我们再来看看还有其他什么情况,ArrayList会抛出CurrentModificationException。
- writeObject:writeObject是序列化时调用的方法,也就是说在在元素序列化时,如果有其他操作引起了modCount发生改变时会抛出并发修改异常。
- Itr的next和remove:在使用迭代器期间,其他操作引起modCount改变时。
- ListItr的previous,next,set,remove,add:同Itr.
- SubList中的所有操作
解决方案
方案一
Iterator<String> iter = list.iterator();
while(iter.hasNext()){
String str = iter.next();
if( str.equals("B")){
iter.remove();
}
}
方案二
List<String> list = new CopyOnWriteArrayList<String>();
list.add("A");
list.add("B");
list.add("C");
for (String s : list) {
if (s.equals("B")) {
list.remove(s);
}
}