一、异常产生
当我们迭代一个ArrayList或者HashMap时,如果尝试对集合做一些修改操作(例如删除元素),可能会抛出java.util.ConcurrentModificationException的
异常。
import java.util.Iterator;
import java.util.List;
public class AddRemoveListElement {
public static void main(String args[]) {
List<String> list = new ArrayList<String>();
list.add("A");
list.add("B");
for (String s : list) {
if (s.equals("B")) {
list.remove(s);
}
}
//foreach循环等效于迭代器
/*Iterator<String> iterator=list.iterator();
while(iterator.hasNext()){
String s=iterator.next();
if (s.equals("B")) {
list.remove(s);
}
}*/
}
}
二、异常原因
ArrayList的父类AbstarctList中有一个域modCount,每次对集合进行修改(增添元素,删除元素……)时都会modCount++
而foreach的背后实现原理其实就是Iterator(关于Iterator可以看Java Design Pattern: Iterator),等同于注释部分代码。在这里,迭代ArrayList的Iterator中有一个变量expectedModCount,该变量会初始化和modCount相等,但如果接下来如果集合进行修改modCount改变,就会造成expectedModCount!=modCount,此时就会抛出java.util.ConcurrentModificationException异常
过程如下图:
三、异常的解决
1、单线程环境
好,现在我们已经基本了解了异常的发送原因了。接下来我们来解决它。
我很任性,我就是想在迭代集合时删除集合的元素,怎么办?
Iterator<String> iter = list.iterator();
while(iter.hasNext()){
String str = iter.next();
if( str.equals("B") )
{
iter.remove();
}
}
细心的朋友会发现Itr中的也有一个remove方法,实质也是调用了ArrayList中的remove,但增加了expectedModCount = modCount;保证了不会抛出java.util.ConcurrentModificationException异常。
但是,这个办法的有两个弊端
1.只能进行remove操作,add、clear等Itr中没有。
2.而且只适用单线程环境。
2、多线程环境
方法一:迭代前加锁,解决了多线程问题,但还是不能进行迭代add、clear等操作
public class Test2 {
static List<String> list = new ArrayList<String>();
public static void main(String[] args) {
list.add("a");
list.add("b");
list.add("c");
list.add("d");
new Thread() {
public void run() {
Iterator<String> iterator = list.iterator();
synchronized (list) {
while (iterator.hasNext()) {
System.out.println(Thread.currentThread().getName()
+ ":" + iterator.next());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
}.start();
new Thread() {
public synchronized void run() {
Iterator<String> iterator = list.iterator();
synchronized (list) {
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println(Thread.currentThread().getName()
+ ":" + element);
if (element.equals("c")) {
iterator.remove();
}
}
}
};
}.start();
}
}
方法二:采用CopyOnWriteArrayList,解决了多线程问题,同时可以add、clear等操作
public class Test2 {
static List<String> list = new CopyOnWriteArrayList<String>();
public static void main(String[] args) {
list.add("a");
list.add("b");
list.add("c");
list.add("d");
new Thread() {
public void run() {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(Thread.currentThread().getName()
+ ":" + iterator.next());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
}.start();
new Thread() {
public synchronized void run() {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println(Thread.currentThread().getName()
+ ":" + element);
if (element.equals("c")) {
list.remove(element);
}
}
};
}.start();
}
}
CopyOnWriteArrayList也是一个线程安全的ArrayList,其实现原理在于,每次add,remove等所有的操作都是重新创建一个新的数组,再把引用指向新的数组。