fail-fast机制,即快速失败机制,是java集合框架中的一种错误检测机制。多线程下用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除),则会抛出Concurrent Modification Exception。fail-fast机制并不保证在不同步的修改下一定会抛出异常,这种机制一般仅用于检测bug。

那么在实际测试代码当中是如何表现的呢?


先说结论:在用for遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除),则会抛出ConcurrentModificationException。在单线程下用迭代器遍历修改,则不会报错。在多线程环境下则会报错。

  原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

  这里异常的抛出条件是检测到 modCount!=expectedmodCount这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。

我们来检测单线程下用增强for循环来更新数据会不会报错:

import java.util.*;
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for(int i=0;i<10;i++) {
list.add(i);
}
for(int n:list){
if(n==6){
list.set(n, 16);
}
}
System.out.println(list.toString());
}

输出是[0, 1, 2, 3, 4, 5, 16, 7, 8, 9]

那如果在遍历中删除参数呢?

public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for(int i=0;i<10;i++) {
list.add(i);
}
for(int n:list){
if(n==6){
list.remove(n);
}
}
System.out.println(list.toString());
}
->Exception in thread "main" java.util.ConcurrentModificationException

这是为什么呢?让我们来看看set的源码:

public E set(int index, E element) {
rangeCheck(index); E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}

我们发现,进行set操作的时候,modCount并没有自增,所以不会报错。

那如果用迭代器进行遍历remove呢?

public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for(int i=0;i<10;i++) {
list.add(i);
}
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
if (it.next() == 6) {
it.remove();
}
}
System.out.println(list.toString());
}
->[0, 1, 2, 3, 4, 5, 7, 8, 9]

这是因为:

  • 迭代器是作为当前集合的内部类实现的,当迭代器创建的时候保持了当前集合的引用;
  • 集合内部维护一个int变量modCount,用来记录集合被修改的次数,比如add,remove等都会使该字段递增;
  • modCount这个参数记录了某个List改变大小的次数,如果modCount改变的不符合预期,那么就会抛出异常。
  • 迭代器内部也维护着当前集合的修改次数的字段,迭代器创建时该字段初始化为集合的modCount值
  • 当每一次迭代时,迭代器会比较迭代器维护的字段和modCount的值是否相等,如果不相等就抛ConcurrentModifiedException异常;
  • 当然,如果用迭代器调用remove方法,那么集合和迭代器维护的修改次数都会递增,以保持两个状态的一致。

如下面ArrayList继承的AbstractList源码所示:

  • 定义了modCount
protected transient int modCount = 0;

ArrayList源码:

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();
}
}
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] = null; // clear to let GC do its work return oldValue;
}
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

可以发现进行remove操作时变量modCount自增了

  Ps:java.util包下的集合类都是采用快速失败机制的,不能在多线程下发生并发修改(迭代过程中被修改)。

安全失败(fail—safe)


  采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。

  原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。

  缺点:基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。

  Ps:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。

Java集合框架中的快速失败(fail—fast)机制的更多相关文章

  1. 第49节:Java集合框架中底层文档的List与Set

    Java集合框架中的List与Set // 简书作者:达叔小生 Collection -> Set 无序不重复 -> 无序HashSet,需要排序TreeSet -> List 有序 ...

  2. Java集合框架中底层文档的List与Set

    Java集合框架中的List与Set // 简书作者:达叔小生 Collection -> Set 无序不重复 -> 无序HashSet,需要排序TreeSet -> List 有序 ...

  3. Java集合框架中Map接口的使用

    在我们常用的Java集合框架接口中,除了前面说过的Collection接口以及他的根接口List接口和Set接口的使用,Map接口也是一个经常使用的接口,和Collection接口不同,Map接口并不 ...

  4. Java集合框架中List接口的简单使用

    Java集合框架可以简单的理解为一种放置对象的容器,和数学中的集合概念类似,Java中的集合可以存放一系列对象的引用,也可以看做是数组的提升,Java集合类是一种工具类,只有相同类型的对象引用才可以放 ...

  5. Java中的函数式编程(五)Java集合框架中的高阶函数

    写在前面 随着Java 8引入了函数式接口和lambda表达式,Java 8中的集合框架(Java Collections Framework, JCF)也增加相应的接口以适应函数式编程.   本文的 ...

  6. Java集合框架中的元素

    之前有一篇笔记,讲的是集合和泛型,这几天看Java集合中几个接口的文档,思绪非常混乱,直到看到Oracle的“The Collections Framwork”的页面,条理才清晰些,现在进行整理. 一 ...

  7. java集合框架中contains(),containsKey()和containsValue()的用法:

    List集合的contains()方法用于判断集合中包不包含某个元素,返回值是boolean. Map集合的containsKey()和containsValue()方法和上面的相同. 示例: pub ...

  8. java集合框架中Set和List的区别

    1. Set 接口实例存储的是无序的,不重复的数据.List 接口实例存储的是有序的,可以重复的元素. 2. Set检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变 <实现类有Ha ...

  9. java集合框架中的去重问题

    对于自定义的类来说,必须要重写hashcode和equals方法 hashcode方法的作用是确定元素在数据结构中的位置,当两个元素的hash值一样时,需要用equals方法判断两个元素是否是一样的, ...

随机推荐

  1. java 初始化顺序问题

    今天在<thinking in java>上面看了关于初始化问题,之前从来都没有深入考虑过,这次算是把它搞明白了,所以记录一下: 这个不是我看到的初始化顺序问题,在网上搜索的时候发现的,感 ...

  2. bug:执行到数据库连接后停止运行,而且不报错的奇怪情况----可能是多方同时访问造成的

    数据库运行过程中奇怪停止. 即执行到“database connected ”停止运行,而且不产生任何报错信息 程序反复检查没有问题,折腾半天解决了: 可能是多个客户端操作同一个表格,多方同时操作造成 ...

  3. 转载:BIO | NIO | AIO

    http://my.oschina.net/bluesky0leon/blog/132361 也谈BIO | NIO | AIO (Java版)   转载自:zheng-lee博客 发布时间: 201 ...

  4. IOS runtime动态运行时二

    在C#.Java中有编译时多态和运行时多态,在OC中,只有运行时的多态,这与它的运行机制有关.OC中,方法的调用是通过消息的传递来进行的.在IOS runtime动态运行时一http://www.cn ...

  5. HA_Ship Transcation Log 事务日志传送

    环境准备: 虚拟机3台,INTER-DC, INTER-SQLA, INTER-SQLB 创建域帐户 INTER\MSSQLSERVER.SERVICE,INTER\AGENT.SERVICE,分别添 ...

  6. PHP项目学习1

    最近在学习PHP,看了<轻松学PHP>,2天看完,学习了很多基础知识,可是没有出什么成果.然后看<PHP项目开发全程实录>,里面讲到一个online影视365网,刚好有一个朋友 ...

  7. 使用重绘项美化WinForm中的控件

    如果你觉得项目中的ComboBox.ListBox或其它的Winforms控件不能满足你的显示要求,包括窗体在内很多控件都支持重绘修改显示样式.下面的示例完成对ComBox数据项的重绘,希望能起到抛砖 ...

  8. WPF月视图控件

    简介 在做一个应用时,需要做成日历月视图的形式.自己做较麻烦,于是上网找找看,在CodeProject上发现了这个Quick and Simple WPF Month-view Calendar,可是 ...

  9. RESTful api 设计规范

    该仓库整理了目前比较流行的 RESTful api 设计规范,为了方便讨论规范带来的问题及争议,现把该文档托管于 Github,欢迎大家补充!! Table of Contents RESTful A ...

  10. oracle数据库的安装与连接关键点

    一.window xp系统上安装Oracle Database 10G 解锁Scott.Hr账号并重置口令 远程连接数oracle数据库地址 二.在Mac系统上使用Navicat远程连接oracle数 ...