异常抛出原因
在使用remove方法对ArrayList进行删除操作时,会抛出此异常。
代码分析
测试用户类:
package sort;
public class User implements Comparable{
private int id;
private String name;
private int birthDay;
public User(int id, String name, int birthDay) {
this.id = id;
this.name = name;
this.birthDay = birthDay;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public int getBirthDay() {
return birthDay;
}
@Override
public int compareTo(User user) {
return user.getId() - this.getId();
}
}
各种remove方法测试:
package sort;
import com.google.common.collect.Lists;
import java.util.Iterator;
import java.util.List;
public class SortTest1 {
public static void main(String[] args) {
foreachRemove();
iteratorRemove();
iteratorHasNextCheck();
}
private static void iteratorHasNextCheck() {
List list = initUserList();
Iterator iterator = list.iterator();
while(iterator.hasNext()){
User user = iterator.next();
if("四".equals(user.getName())){
list.remove(user);
}else{
System.out.println(user.getName());
}
}
}
private static void iteratorRemove() {
try {
List list = initUserList();
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
User user = iterator.next();
if (user.getBirthDay() < 20040101) {
iterator.remove();
}
}
System.out.println(list.size());
} catch (Exception e) {
e.printStackTrace();
System.out.println("iteratorRemove failed");
}
}
private static void foreachRemove() {
try {
List list = initUserList();
for (User user : list) {
if (user.getBirthDay() < 20040101) {
list.remove(user);
}
}
System.out.println(list.size());
} catch (Exception e) {
e.printStackTrace();
System.out.println("foreachRemove failed");
}
}
private static List initUserList() {
List list = Lists.newArrayList();
list.add(new User(1,"一", 20010101));
list.add(new User(2,"二", 20020202));
list.add(new User(3,"三", 20030303));
list.add(new User(4,"四", 20040404));
list.add(new User(5,"五", 20050505));
return list;
}
}
结果:
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at sort.SortTest1.foreachRemove(SortTest1.java:49)
at sort.SortTest1.main(SortTest1.java:11)
foreachRemove failed
2
一
二
三
在分析结果前,先贴出ArrayList的迭代器源码
private class Itr implements Iterator {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
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];
}
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();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
分析:
1.可以看到hasNext()方法主要是比对当前元素的数组下标与迭代器元素的个数。
2.next方法要先检查ArrayList的操作数(modCount,ArrayList父类AbstractList的成员变量,
这个成员变量记录着集合的修改次数,也就每次add或者remove它的值都会加1)有没有改变,如果改变就会抛出ConcurrentModificationException异常。
- foreachRemove里使用了ArrayList的remove方法,modCount发生了变化,所以抛出异常。
- iteratorRemove里使用了ArrayList内部类Itr的remove方法,modCount未发生变化,所以正常执行。
- iteratorHasNextCheck里正好移除了第4个元素,此时虽然modCount变化了,但是元素总数与索引下标都是4,此时hasNext()返回false,所以不会往下执行。
- iteratorHasNextCheck里如果移除前3个元素,依旧会抛出ConcurrentModificationException异常。
线上问题
线上使用guava本地缓存,并且每次拿出数据会再次进行Collections.sort()排序,导致抛出此异常。
伪代码
:
package sort;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class SortTest2 {
private static Map> map = Maps.newHashMap();
public static void main(String[] args) {
buildMap();
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5,
0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
for (int i=0; i<5; i++) {
executor.execute(() -> {
try {
Collections.sort(map.get("list"));
Thread.sleep(1000L);
} catch (Exception e) {
e.printStackTrace();
System.out.println("sort thread error");
}
});
}
executor.shutdown();
}
private static void buildMap() {
map.put("list", initUserList());
}
private static List initUserList() {
List list = Lists.newArrayList();
list.add(new User(3,"三", 20030303));
list.add(new User(2,"二", 20020202));
list.add(new User(4,"四", 20040404));
list.add(new User(1,"一", 20010101));
list.add(new User(5,"五", 20050505));
return list;
}
}
结果(报错数量不定,取决于并发数与排序执行时间):
java.util.ConcurrentModificationException
at java.util.ArrayList.sort(ArrayList.java:1464)
at java.util.Collections.sort(Collections.java:141)
at sort.SortTest2.lambda$main$0(SortTest2.java:24)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
sort thread error
java.util.ConcurrentModificationException
at java.util.ArrayList.sort(ArrayList.java:1464)
at java.util.Collections.sort(Collections.java:141)
at sort.SortTest2.lambda$main$0(SortTest2.java:24)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
sort thread error
java.util.ConcurrentModificationException
at java.util.ArrayList.sort(ArrayList.java:1464)
at java.util.Collections.sort(Collections.java:141)
at sort.SortTest2.lambda$main$0(SortTest2.java:24)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
sort thread error
ArrayList中的sort()方法
@Override
@SuppressWarnings("unchecked")
public void sort(Comparator super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
主要调用了Arrays的sort()方法,此排序方法本文暂不介绍,后续modCount进行了自增,此处在多个线程一起执行下会出现问题,抛出ConcurrentModificationException异常。