ConcurrentModificationException 记录与分析
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
Iterator<Integer> ite = list.iterator();
while (ite.hasNext()) {
Integer number = ite.next();
if (number==1){
list.remove(number);
}
}
}
IDEA运行结果
Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1042)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996)
at com.itheima.yuxi.TestConcurrentModificationException.main(TestConcurrentModificationException.java:12)
分析异常位置在checkForComodification()方法
final void checkForComodification () {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
所以我们从源码入手进一步分析其原因
Arraylist 没有iterator(),在Arraylist的父类找到查看
public Iterator<E> iterator() {
return new Itr();
}
iterator()返回Itr()对象引用,接着我们查看Itr类的实现
private class Itr implements Iterator<E> {
/**
* 后续调用 next 返回的元素的索引
*/
int cursor = 0;
/**
* 表示上一个访问的元素的索引
*/
int lastRet = -1;
/**
* 表示对ArrayList修改次数的期望值,它的初始值为modCount。
* modCount是AbstractList类中的一个成员变量
*
*/
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();
}
}
modCount作为一个成员变量,记录对list的修改次数,对集合的删除remove(int index)和新增add(int index, E element)都会modCount++;
protected transient int modCount = 0;
我们查看报错的代码,Iterator
public boolean hasNext() {
return cursor != size;
}
接下来调用next()方法查看索引为0的元素
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];
}
首先调用checkForComodification()方法,cursor+1,接着将cursor-1的值赋给lastRet,最后获取到lastRet处的元素,初始值cursor为0,lastRet为-1,那么调用一次之后,cursor的值为1,lastRet的值为0,此时,modCount为1,expectedModCount也是1(执行add操作+1)..
接下来我们判断number是否为1,如果是则调用remove();
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
found: {
if (o == null) {
for (; i < size; i++)
if (es[i] == null)
break found;
} else {
for (; i < size; i++)
if (o.equals(es[i]))
break found;
}
return false;
}
fastRemove(es, i);
return true;
}
fastRemove()对元素实际删除,modCount+1,集合长度-1,并将最后一个元素引用置为null以方便垃圾收集器进行回收工作。
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
此时各个变量的值:
对于iterator,其expectedModCount为1,cursor的值为1,lastRet的值为0.
对于list size为0,modCount为2
删除成功,调用while循环的hasNext()方法,cursor != size;继续调用next(),其方法的第一句:checkForComodification(); 抛出ConcurrentModificationException异常!!
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
原因分析:list.remove()导致modCount与expectedModCount不同,注意:像使用for-each进行迭代实际上也会出现这种问题.
解决方案
单线程情况:
在ite类中存在一个remove方法
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();
}
这个方法实际调用了ArrayList.this.remove()方法,但其中增加了一个操作
expectedModCount = modCount;
所以,ArrayList删除元素要用迭代器的remove();
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
Iterator<Integer> ite = list.iterator();
while (ite.hasNext()) {
Integer number = ite.next();
if (number==1){
ite.remove();
}
}
}
在以上单线程的情况下,查看多线程是否有影响
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
new Thread(new Runnable() {
@Override
public void run() {
Iterator<Integer> ite = list.iterator();
while (ite.hasNext()) {
Integer number = ite.next();
System.out.println(number);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
Iterator<Integer> ite = list.iterator();
while (ite.hasNext()) {
Integer number = ite.next();
if (number == 1) {
ite.remove();
}
}
}
}).start();
}
IDEA运行结果
Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1042)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:996)
at com.itheima.yuxi.TestThreadCM$1.run(TestThreadCM.java:18)
at java.base/java.lang.Thread.run(Thread.java:835)
多线程在修改集合和遍历集合时,通过iterator()访问,每个对象有不同的ite,可理解为expectedModCount 为每个线程私有,以上两个线程,线程1进行遍历,线程2进行修改,线程2改变后会导致expectedModCount 和 modCount的值自增,但是线程1expectedModCount没有自增,因此modCount != expectedModCount,抛出ConcurrentModificationException异常!!
给出两种解决方法
- 使用iterator()迭代时使用synchronized同步
- 使用并发容器CopyOnWriteArrayList(删除时用list.remove(number);有加锁操作)替代ArrayList和Vector
参考资料Matrix海子
标签:java,记录,ConcurrentModificationException,ArrayList,ite,cursor,modCount,new From: https://www.cnblogs.com/OKGOsky/p/17008848.html