什么是快速失败的故障安全c++迭代器器

fail-fast和fail-safe的介绍和区别
fail-fast和fail-safe
前段时间公司招的实习生在使用迭代器遍历的时候,对集合内容进行了修改,从而抛出ConcurrentModificationException. 然后给他讲解之余也整理了这一篇文章.
fail-fast ( 快速失败 )
在使用迭代器遍历一个集合对象时,比如增强for,如果遍历过程中对集合对象的内容进行了修改(增删改),会抛出 ConcurrentModificationException 异常.
查看ArrayList源代码,在next方法执行的时候,会执行checkForComodification()方法
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
if (i &= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementD
if (i &= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个modCount变量,
集合中在被遍历期间如果内容发生变化,就会改变modCount的值,
每当迭代器使用 hashNext()/next()遍历下一个元素之前,都会检测modCount变量和expectedmodCount值是否相等,
如果相等就返回遍历,否则抛出异常,终止遍历.
for(Person person : Persons){
if(person.getId()==2)
student.remove(person);
这里异常的抛出条件时检测到modCount = expectedmodCount 这个条件.
如果集合发生变化时修改modCount值, 刚好有设置为了expectedmodCount值, 则异常不会抛出.(比如删除了数据,再添加一条数据)
for(Person person : Persons){
if(person.getId()==2){
Persons.remove(person);
Persons.add(new Person());
所以不能依赖于这个异常是否抛出而进行并发操作的编程, 这个异常只建议检测并发修改的bug.
使用场景 :
java.util包下的集合类都是快速失败机制的, 不能在多线程下发生并发修改(迭代过程中被修改).
fail-safe ( 安全失败 )
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先copy原有集合内容,在拷贝的集合上进行遍历.
由于迭代时是对原集合的拷贝的值进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会出发ConcurrentModificationException
基于拷贝内容的优点是避免了ConcurrentModificationException,但同样地, 迭代器并不能访问到修改后的内容 (简单来说就是, 迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的)
java.util.concurrent包下的容器都是安全失败的,可以在多线程下并发使用,并发修改.
fail-fast 与 fail-safe 机制有什么区别
没有更多推荐了,快速失败(Java集合)
在JDK中,查看集合有很多关于快速失败的描述:
注意,此实现不是同步的。如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步。(结构上的修改是指添加或删除一个或多个映射关系的任何操作;仅改变与实例已经包含的键关联的值不是结构上的修改。)这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的非同步访问,如下所示:
Map m = Collections.synchronizedMap(new HashMap(...));由所有此类的“collection 视图方法”所返回的迭代器都是快速失败 的:在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器本身的 remove 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒在将来不确定的时间发生任意不确定行为的风险。
注意,迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。快速失败:对于非并发集合来说,在其进行迭代时,例如iterator迭代时,iterator是另起一个线程,若有其他线程(如Collection)进行结构修改(修改了增减了集合中的内容),这个迭代会马上感知到,并且立即抛出 ConcurrentModificationException 异常,而不是迭代完成后才告诉你出错了,引起快速失败。若用iterator进行修改则不会出现这个问题,如iterator.move();也就是说涉及到了多个线程间的同步问题
示例代码:
package CoreJ
import java.util.HashM
import java.util.H
import java.util.I
import java.util.M
import java.util.Map.E
import java.util.concurrent.ConcurrentHashM
public class ConcurrentHashMapTest {
public static void main(String[] args) {
Hashtable&String, String& table = new Hashtable&String, String&();
table.put("a", "vb");
table.put("s", "er");
table.put("d", "fg");
table.remove("d");
Iterator&Entry&String, String&& iterator = table.entrySet().iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next().getValue());
iterator.remove();//采用iterator直接进行修改 程序正常
// table.put("c", "wc");直接从hashtable增删数据就会报错
// table.remove("d");直接从hashtable增删数据就会报错 hashtable,hashmap等非并发集合 如果在迭代过程中增减了数据,
System.out.println("-----------");
HashMap&String, String& hashmap = new HashMap&String, String&();
hashmap.put("a", "vb");
hashmap.put("s", "er");
hashmap.put("d", "fg");
Iterator&Entry&String, String&& iterators = hashmap.entrySet()
.iterator();
while (iterators.hasNext()) {
System.out.println(iterators.next().getValue());
iterators.remove();// 正常
// hashmap.remove("d");//直接从hashtable增删数据就会报错 hashtable,hashmap等非并发集合,如果在迭代过程中增减了数据,会快速失败 (一检测到修改,马上抛异常)
System.out.println("-----------");
ConcurrentHashMap&String, String& map = new ConcurrentHashMap&String, String&();
map.put("a", "vb");
map.put("s", "er");
map.put("d", "fg");
Iterator&Entry&String, String&& mapiterator = map.entrySet().iterator();
while (mapiterator.hasNext()) {
System.out.println(mapiterator.next().getValue());
map.remove("d");// 正常 并发集合不存在快速失败问题
map.put("c", "wc");// 正常 并发集合不存在快速失败问题
System.out.println("-----------");
for (Map.Entry&String, String& entry : map.entrySet()) {
System.out.println(entry.getValue() + ", " + entry.getKey());
System.out.println("-----------");
for (Map.Entry&String, String& entry : table.entrySet()) {
System.out.println(entry.getValue() + ", " + entry.getKey());
* final Entry&K,V& nextEntry() { if (modCount != expectedModCount) throw
* new ConcurrentModificationException(); Entry&K,V& e = if (e ==
* null) throw new NoSuchElementException();
* if ((next = e.next) == null) { Entry[] t = while (index & t.length
* && (next = t[index++]) == null) ; } current = }
Java 集合类的快速失败
Java提高篇(三四)-----fail-fast机制
java的快速失败模式和安全失败模式
快速失败和安全失败
java中的fail-fast(快速失败)机制
44.黑马程序员-集合面试题
Java - 快速失败和安全失败
Java基础之——快速失败&安全失败(最全的总结)
Java集合的快速失败和安全失败
Java 的多线程访问常见异常--fast-lost (快速失败 )
没有更多推荐了,社会化媒体
了解更多>>
桂ICP备 号
阅读下一篇
自媒体运营攻略
行业经验交流
Hi,在你登录以后,就可以永久免费的收藏任何您感兴趣的内容,关注感兴趣的作者!
手机注册或邮箱注册
点击按钮进行验证
请输入正确的邮箱
已有帐号请点击
帐号创建成功!
我们刚刚给你发送了一封验证邮件
请在48小时内查收邮件,并按照提示验证邮箱
感谢你对微口网的信任与支持
你输入的邮箱还未注册
还没有帐号请点击
点击按钮进行验证
你输入的邮箱还未注册
又想起来了?
你已成功重置密码,请妥善保管,以后使用新密码登录
邮件发送成功!
我们刚刚给你发送了一封邮件
请在5分钟内查收邮件,并按照提示重置密码
感谢你对微口网的信任与支持
对不起,你的帐号尚未验证
如果你没有收到邮件,请留意垃圾箱 或
意见与建议
请留下您的联系方式
* 留下您正确的联系方式,以便工作人员尽快与你取得联系
转藏至我的藏点java ArrayList 迭代器快速失败源码分析
由 匿名 (未验证)
先来看一个例子:
void test2() {
ArrayList&String& list = new ArrayList&String&();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
Iterator&String& iterator = list.iterator();
while(iterator.hasNext()){
String str = (String) iterator.next();
if(str.equals("d")){
list.remove(str);
iterator.remove();
System.out.println(str);
这段代码运行不会报错,但输出不符合我们的预期。我们预期应该是输出:a,b,c,e
如果我们换成删除c,则会报 java.util.ConcurrentModificationException 异常
为什么会出现这个问题呢?
我们跟踪到源码可以发现,ArrayList 类有一个成员变量 modCount 继承自&AbstractList 类
AbstractList 类:
1 protected transient int modCount = 0;
transient 关键字修饰,表明变量不必参与序列化。这个变量的作用是记录list 结构被修改的次数在add 和 remove 方法里面都能看到它的身影add方法:
//add方法会调用此方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length & 0)
grow(minCapacity);
remove(int)方法:
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved & 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = // clear to let GC do its work
return oldV
remove(Object) 方法:
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index & index++)
if (elementData[index] == null) {
fastRemove(index);
for (int index = 0; index & index++)
if (o.equals(elementData[index])) {
fastRemove(index);
而ArrayList的迭代器是ArrayList的一个内部类:
public Iterator&E& iterator() {
return new Itr();
Itr 源码:
private class Itr implements Iterator&E& {
// index of next element to return
int lastRet = -1; // index of l -1 if no such
int expectedModCount = modC
public boolean hasNext() {
return cursor !=
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
if (i &= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementD
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();
ArrayList.this.remove(lastRet);
cursor = lastR
lastRet = -1;
expectedModCount = modC
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer&? super E& consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.
if (i &= size) {
final Object[] elementData = ArrayList.this.elementD
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
lastRet = i - 1;
checkForComodification();
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
可以看到迭代器有一个内部变量 expectedModCount &是拷贝的&modCount&
next() 会检查modCount 是否改变,若改变则抛出&ConcurrentModificationException 异常
所以文章开头的第一种情况就可以解释了,之所以删除d’没有报错是因为,d是倒数第二个元素,删除之后,游标&cursor == size
在hasNext方法执行的时候就已经返回了false,没有执行next()方法
而改成c以后,next()会被执行,从而检查出modCount 被修改,抛出异常。
而调用迭代器的remove 方法就不会出现这种情况,因为它更新了expectedModCount,使之与 modCount 保持一致
迭代器的remove方法:
1 public void remove() {
if (lastRet & 0)
throw new IllegalStateException();
checkForComodification();
ArrayList.this.remove(lastRet);
cursor = lastR
lastRet = -1;            //更新expectedModCount
expectedModCount = modC
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
快速失败是为了保证数据的安全性,防止并发修改出现的错误。HashMap 等等非线程安全的集合也有同样的机制。
并发情况下应该使用线程安全的集合工具,例如CopyOnWriteArrayList,ConcurrentHashMap 等集合迭代器快速失败行为及CopyOnWriteArrayList - 风一样的码农 - 博客园
随笔 - 192, 文章 - 8, 评论 - 232, 引用 - 0
以下内容基于jdk1.7.0_79源码;
什么是集合迭代器快速失败行为
以ArrayList为例,在多线程并发情况下,如果有一个线程在修改ArrayList集合的结构(插入、移除...),而另一个线程正在用迭代器遍历读取集合中的元素,此时将抛出ConcurrentModificationException异常立即停止迭代遍历操作,而不需要等到遍历结束后再检查有没有出现问题;
ArrayList.Itr迭代器快速失败源码及例子
查看ArrayList的Itr迭代器源码,可以看到Itr为ArrayList的私有内部类,有一个expectedModCount成员属性,在迭代器对象创建的时候初始化为ArrayList的modCount,即当迭代器对象创建的时候,会将集合修改次数modCount存到expectedModCount里,然后每次遍历取值的时候,都会拿ArrayList集合修改次数modCount与迭代器的expectedModCount比较,如果发生改变,说明集合结构在创建该迭代器后已经发生了改变,直接抛出ConcurrentModificationException异常,如下代码;
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
先举一个不是多线程的简单例子,在创建迭代器后,往ArrayList插入一条数据,然后利用迭代器遍历,如下代码,将抛出ConcurrentModificationException异常:
package com.pichen.basis.
import java.util.ArrayL
import java.util.I
import java.util.L
public class Main {
public static void main(String[] args) {
List&Integer& list = new ArrayList&Integer&();
for(int i = 0; i & 10; i++){
list.add(i);
Iterator&Integer& iterator = list.iterator();
list.add(10);
while(iterator.hasNext()){
iterator.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.pichen.basis.col.Main.main(Main.java:18)
再来个多线程的例子,我们创建一个t1线程,循环往集合插入数据,另外主线程获取集合迭代器遍历集合,如下代码,在遍历的过程中将抛出ConcurrentModificationException异常:
package com.pichen.basis.
import java.util.ArrayL
import java.util.I
import java.util.L
class ThreadTest implements Runnable{
private List&Integer&
public ThreadTest(List&Integer& list) {
this.list =
public void run() {
while(true){
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
this.list.add(10);
public class Main {
public static void main(String[] args) throws InterruptedException {
List&Integer& list = new ArrayList&Integer&();
for(int i = 0; i & 10; i++){
list.add(i);
ThreadTest t1 = new ThreadTest(list);
new Thread(t1).start();
Iterator&Integer& iterator = list.iterator();
while(iterator.hasNext()){
System.out.print(iterator.next() + " ");
Thread.sleep(80);
结果打印:
0 1 2 3 4 5 6 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.pichen.basis.col.Main.main(Main.java:44)
ConcurrentModificationException异常解决办法
对ArrayList集合修改次数modCount加锁或使用CopyOnWriteArrayList替换ArrayList,建议使用CopyOnWriteArrayList;
另外除了List集合外,其它集合像ConcurrentHashMap、CopyOnWriteArraySet也可以避免抛出ConcurrentModificationException异常;
什么是CopyOnWriteArrayList
看名字就知道,在往集合写操作的时候,复制集合;更具体地说,是在对集合结构进行修改的操作时,复制一个新的集合,然后在新的集合里进行结构修改(插入、删除),修改完成之后,改变原先集合内部数组的引用为新集合即可;
CopyOnWriteArrayList补充说明
CopyOnWriteArrayList类实现List&E&, RandomAccess, Cloneable, java.io.Serializable接口
与ArrayList功能类似,同样是基于动态数组实现的集合;
使用CopyOnWriteArrayList迭代器遍历的时候,读取的数据并不是实时的;
每次对集合结构进行修改时,都需要拷贝数据,占用内存较大;
先看个简单的例子,add方法,如下,调用了Arrays.copyOf方法,拷贝旧数组到新数组,然后修改新数组的值,并修改集合内部数组的引用:
public boolean add(E e) {
final ReentrantLock lock = this.
lock.lock();
Object[] elements = getArray();
int len = elements.
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] =
setArray(newElements);
return true;
} finally {
lock.unlock();
再查看CopyOnWriteArrayList迭代器类的实现,部分代码如下, 在创建COWIterator迭代器的时候,仔细查看其构造器源码,需要将集合内部数组的引用传给迭代器对象,由于在集合修改的时候,操作都是针对新的拷贝数组,所以迭代器内部旧数组对象不会改变,保证迭代期间数据不会混乱(虽然不是实时的数据):
private static class COWIterator&E& implements ListIterator&E& {
/** Snapshot of the array */
private final Object[]
/** Index of element to be returned by subsequent call to next.
private int
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialC
snapshot =
利用前面抛出ConcurrentModificationException的例子,验证使用CopyOnWriteArrayList,
首先,创建一个t1线程,循环往集合插入数据,另外主线程获取集合迭代器遍历集合,代码如下,成功运行,并打印出了旧集合的数据(注意数据并不是实时的)。
package com.pichen.basis.
import java.util.I
import java.util.L
import java.util.concurrent.CopyOnWriteArrayL
class ThreadTest implements Runnable{
private List&Integer&
public ThreadTest(List&Integer& list) {
this.list =
public void run() {
while(true){
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
this.list.add(10);
public class Main {
public static void main(String[] args) throws InterruptedException {
List&Integer& list = new CopyOnWriteArrayList&Integer&();
for(int i = 0; i & 10; i++){
list.add(i);
ThreadTest t1 = new ThreadTest(list);
new Thread(t1).start();
Iterator&Integer& iterator = list.iterator();
while(iterator.hasNext()){
System.out.print(iterator.next() + " ");
Thread.sleep(80);

我要回帖

更多关于 迭代器大小 的文章

 

随机推荐