快速失败(fail—fast)
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
原理:假设有两个线程(线程A,线程B),其中线程A负责遍历list、线程B修改list。在使用迭代器遍历时,AbstractList类的内部类Itr中定义了变量expectedModCount,初始值为集合的modCount值。在线程A在遍历list过程中,线程启动,同时线程B增加一个元素,这时modCount的值发生改变(modCount = N + 1)。 线程A继续遍历执行next方法,checkForComodification方法发现expectedModCount = N , 而modCount = N + 1,两者不等,这时就抛出ConcurrentModificationException 异常,从而产生fail-fast机制。
单线程环境下ArrayList发生fail-fast例子
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0 ; i < 10 ; i++ ) {
list.add(i + "");
}
Iterator<String> iterator = list.iterator();
int i = 0 ;
while(iterator.hasNext()) {
if (i == 3) {
list.remove(3);
}
System.out.println(iterator.next());
i ++;
}
}
该段代码定义了一个Arraylist集合,并使用迭代器遍历,在遍历过程中,刻意在某一步迭代中remove一个元素,这个时候,就会发生fail-fast。
HashMap发生fail-fast:
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
for (int i = 0 ; i < 10 ; i ++ ) {
map.put(i+"", i+"");
}
Iterator<Entry<String, String>> it = map.entrySet().iterator();
int i = 0;
while (it.hasNext()) {
if (i == 3) {
map.remove(3+"");
}
Entry<String, String> entry = it.next();
System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
i++;
}
}
该段代码定义了一个hashmap对象并存放了10个键值对,在迭代遍历过程中,使用map的remove方法移除了一个元素,导致抛出了 ConcurrentModificationException异常
多线程环境下
public class FailFastTest {
public static List<String> list = new ArrayList<>();
private static class MyThread1 extends Thread {
@Override
public void run() {
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
String s = iterator.next();
System.out.println(this.getName() + ":" + s);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
super.run();
}
}
private static class MyThread2 extends Thread {
int i = 0;
@Override
public void run() {
while (i < 10) {
System.out.println("thread2:" + i);
if (i == 2) {
list.remove(i);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
i ++;
}
}
}
public static void main(String[] args) {
for(int i = 0 ; i < 10;i++){
list.add(i+"");
}
MyThread1 thread1 = new MyThread1();
MyThread2 thread2 = new MyThread2();
thread1.setName("thread1");
thread2.setName("thread2");
thread1.start();
thread2.start();
}
}
启动两个线程,分别对其中一个对list进行迭代,另一个在线程1的迭代过程中去remove一个元素,结果也是抛出了java.util.ConcurrentModificationException
总结
安全失败(fail—safe)
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。
缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但是需要复制集合,产生大量的无效对象,内存消耗大,同时迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。