2019独角兽企业重金招聘Python工程师标准>>>

一、fail-fast机制(快速报错机制)

这是《Java编程思想》中关于快速报错机制的描述

Java容器有一种保护机制,能够防止多个进程同时修改同一个容器的内容。如果在你迭代遍历容器的过程中,另一个进程介入其中,并且插入、删除或者修改此容器内的某个对象,那么就会出现问题:也许迭代过程中已经处理过容器中的该元素了,也许还没处理,也许在调用size()之后容器的尺寸收缩了——还有许多灾难情景。Java容器类类库采用快速报错(fail-fast)机制。它会探查容器上的任何除了你的进程所进行的操作以外的所有变化,一旦它发现其它进程修改了容器,就会立刻抛出ConcurrentModificationException异常。这就是“快速报错”的意思——即,不是使用复杂的算法在事后来检查问题。——from《Java编程思想》p517

下面以一个demo开始,来理解快速报错机制。

二、遍历容器的几种方式

程序功能:分别使用for,foreach,iterator来遍历(迭代)容器,然后删除其中的值为”傻强”这个元素。

public class TestTest {private List<String> list;/** * 初始化操作 */@Beforepublic void setUp(){list = new ArrayList<String>();list.add("刘德华");        list.add("周润发");list.add("傻强");list.add("古天乐");list.add("刘青云");System.out.println(list);}/** * Demo1:使用for循环删除元素 */@Testpublic void testFor(){          for(int i=0;i<list.size();i++){//删除傻强if("傻强".equals(list.get(i))){list.remove(i);}}System.out.println(list);}/** * Demo2:使用foreach删除元素【错误】 */@Testpublic void testForeach(){      for (String s : list) {//删除傻强if("傻强".equals(s)){list.remove(s);}}System.out.println(list);}/** * Demo3:使用Iterator和Iterator的remove()删除元素 */@Testpublic void testIterator(){Iterator<String> iterator = list.iterator();while(iterator.hasNext()){String s= iterator.next();//删除lisiif("傻强".equals(s)){iterator.remove();//使用迭代器的remove()}}System.out.println(list);}/** * Demo4:使用Iterator和集合的remove删除元素【错误】 */@Testpublic void testIterator2(){Iterator<String> iterator = list.iterator();while(iterator.hasNext()){String s= iterator.next();//删除傻强if("傻强".equals(s)){list.remove(s);//使用集合的remove}}System.out.println(list);}/** * Demo5:获得iterator后进行了错误操作【错误】 */@Testpublic void testIterator3(){Iterator<String> iterator = list.iterator();//错误操作list.add("这是错误的行为");while(iterator.hasNext()){String s= iterator.next();//删除傻强if("傻强".equals(s)){iterator.remove();}}System.out.println(list);}
}

结果:只有Demo1和Demo3正确。其它demo都会报ConcurrentModificationException异常。这里就用到了fail-fast机制。

三、ArrayList中Iterator源码分析

接下来开始分析。我们先看看ArrayLis中的关于迭代器的代码

    public Iterator<E> iterator() {return new Itr();}/** * An optimized version of AbstractList.Itr * * 覆盖了父类中AbstractList.Itr的实现(优化版) */private class Itr implements Iterator<E> {//下一个要返回元素的索引int cursor;       // index of next element to return//最后一个要返回元素的索引,-1表示不存在int lastRet = -1; // index of last element returned; -1 if no such//记录期望的修改次数(用于保证迭代器在遍历过程中不会有对集合的修改操作(迭代器的自身的remove方法除外))int expectedModCount = modCount;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();}}/** * 检查修改次数 */final void checkForComodification() {//实际的修改次数和期望的修改次数不匹配,则抛出并发修改异常if (modCount != expectedModCount)throw new ConcurrentModificationException();}}

原来,ArrayList从其父类AbstractList继承了一个modCount属性,每当对ArrayList进行修改(add,remove,clear等)时,就会相应的增加modCount的值。
而ArrayList中迭代器的实现类Itr也有一个expectedModCount属性,一旦使用迭代器遍历容器时,就要调用iterator()方法,Itr类也就被初始化,expectedModCount就会被赋予一个与modCount相等的值。
接下来在遍历过程中,每次调用next()方法获取值时都会检查modCount和expectedModCount两个值是否相等(checkForComodification)。如果在遍历过程中出现了对集合的其它修改操作,从而造成两者不等,就会抛出ConcurrentModificationException。这不就是乐观锁的实现思想吗。

另外,我们注意到迭代器自身的remove方法并不会修改modCount的值,这是因为我们通常也会通过迭代遍历去删除某一个指定的元素,所以迭代器中自身提供了该remove方法,并保证该remove方法是安全的,而不希望我们在迭代时使用容器提供remove方法。

四、避免fail-fast

要说明两点
1.虽然ConcurrentModificationException被译为并发修改异常,但这里的”并发”,并非仅仅指的是多线程场景,前面的例子很显然是单线程场景。
在单线程情况下

要确保Iterator遍历过程顺利完成,必须保证遍历过程中不更改集合的内容(Iterator的remove()方法除外)。

多线程情况下

如果要在多线程环境中,在迭代ArrayList的同时也要修改ArrayList,则可以使用
Collections.synchronizedList(List list)或者CopyOnWriteArrayList。

其中CopyOnWriteArrayList是可以避免ConcurrentModificationException。
实际上CopyOnWriteArrayList、ConcurrentHashMap和CopyOnWriteArraySet都使用了可以避免ConcurrentModificationException的技术。

2.迭代器的快速失败行为无法得到保证,它不能保证一定会出现该错误,但是快速失败操作会尽最大努力抛出ConcurrentModificationException异常。因此,为提高此类操作的正确性,我们不能依赖于此异常,而要使用上一条中提到的线程安全的容器。

五、CopyOnWriteArrayList不使用fail-fast机制

通过上面的分析,我么知道了ArrayList一边使用迭代器遍历一边修改是会发生ConcurrentModificationException。但是,ArrayList对应的线程安全容器CopyOnWriteArrayList却不会发生ConcurrentModificationException。那是为什么呢?先来看源码。

    /** * 返回迭代器 * * 返回的迭代器提供了该迭代器被创建时列表的快照。 * 当移动迭代器时,不需要同步。 * 迭代器不支持remove方法 */public Iterator<E> iterator() {return new COWIterator<E>(getArray(), 0);}/** * 内部迭代器的实现类 */private static class COWIterator<E> implements ListIterator<E> {/**数组的快照*/private final Object[] snapshot;/** Index of element to be returned by subsequent call to next. */private int cursor;/** * 私有的构造器 */private COWIterator(Object[] elements, int initialCursor) {cursor = initialCursor;//快照snapshot = elements;}public boolean hasNext() {return cursor < snapshot.length;}public boolean hasPrevious() {return cursor > 0;}@SuppressWarnings("unchecked")public E next() {if (! hasNext())throw new NoSuchElementException();return (E) snapshot[cursor++];}@SuppressWarnings("unchecked")public E previous() {if (! hasPrevious())throw new NoSuchElementException();return (E) snapshot[--cursor];}public int nextIndex() {return cursor;}public int previousIndex() {return cursor-1;}/** * Not supported. Always throws UnsupportedOperationException. * remove is not supported by this iterator. * 不支持。总是抛出不支持的操作异常 * 迭代器不支持remove方法。 */public void remove() {throw new UnsupportedOperationException();}/** * Not supported. Always throws UnsupportedOperationException. * set is not supported by this iterator. * 不支持。总是抛出不支持的操作异常 * 迭代器不支持set方法。 * */public void set(E e) {throw new UnsupportedOperationException();}/** * Not supported. Always throws UnsupportedOperationException. * add is not supported by this iterator. * 不支持。总是抛出不支持的操作异常 * 迭代器不支持aa方法。 * */public void add(E e) {throw new UnsupportedOperationException();}}

原来,CopyOnWriteArrayList中的迭代器在创建之初,会保存一份对原数组的快照,之后所有迭代器的操作都是在快照数组上进行的,原数组一点影响都没有。同时,即使是对快照数组进行操作,也根本不支持迭代器上的修改(add,set,remove)操作。因此,根本就不会发生ConcurrentModificationException。

虽然在迭代时不支持迭代器上的修改操作,但是仍然可以直接使用容器的修改方法,这点恰好跟ArrayList相反。这也很容易解释,因为迭代器操作的是快照数组,在原容器上进行修改也是会创建一个新的数组,因此两者根本不会干扰。

public class Demo {public static void main(String[] args) {CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();//添加元素0-4for(int i=0;i<5;i++){list.add(i+"");}System.out.println(list);//[0, 1, 2, 3, 4]//进行迭代Iterator iterator = list.iterator();while(iterator.hasNext()){String num = (String) iterator.next();//迭代时,删除3if("3".equals(num)){//iterator.remove();//iterator不支持修改方法(add,set,remove)list.remove(num);//使用原容器的remove方法};}System.out.println(list);//[0, 1, 2, 4]}
}

转载于:https://my.oschina.net/javandroid/blog/878235

fail-fast(快速失败/报错机制)-ConcurrentModificationException相关推荐

  1. Android逆向工程:大显神通的Xposed,利用报错机制快速获取程序运行过程

    好久不见,已经一个多月没有更新博客了,博主一直在忙于新的破解项目.在之前的博客中,我们学习了该如何去定位关键代码段,该如何去修改smali代码,以及对各种逆向工具的使用.那么在接下来的博客中,我们将会 ...

  2. ubuntu18.04根目录已满造成开机失败报错Fail to start....

    ubuntu根目录已满造成开机失败报错Fail to start- 由于在安装ubuntu系统是手动分区,没有将/usr分区单独划分出来,造成/usr分区实际占用了根目录/.的空间,基本上通过命令安装 ...

  3. lamuda表达式 list移除空元素_java8 Lambda表达式遍历并移除元素,报错:ConcurrentModificationException的解决办法...

    1.情景展示 已知json对象MainExt 需要把值为空数组的key移除掉,下面是执行代码 执行结果报错:java.util.ConcurrentModificationException 2.原因 ...

  4. CentOS 7安装Development Tools 失败 报错 group tools does not exist. Maybe run: yum groups mark install

    重装centos,以及迁移时,碰见这个问题.如下图: CentOS 7安装Development Tools 失败 报错 group tools does not exist. Maybe run: ...

  5. 高德地图H5 定位失败报错 geolocation time out. Get ipLocation failed解决方案

    高德地图H5 定位失败报错 geolocation time out. Get ipLocation failed的解决方法. 前言:此坑踩得我挺难受的,搞了三天 需求:进入页面,获取用户具体经纬度并 ...

  6. win7 系统更新服务器失败怎么办,Windows7 Update更新失败报错80070002和80070003怎么办?...

    Windows Update更新失败怎么办?一位Win7用户在更新Windows Update时失败了,系统提示错误代码为"80070002"或"80070003&quo ...

  7. pod挂载nas启动失败报错:unable to mount volume xxxx Timeout waiting for mount paths to be created

    深夜你熟睡时,用户打来电话.大哥我在上线我的应用怎么突然起不来了.快帮我看看,再过一个小时店铺就开门了. 核实pod状态 打开电脑登入环境,使用kubectl get pod 查询到用户的pod处于创 ...

  8. jupyter notebook导入numpy 失败 报错:Original error was: DLL load failed while importing _multiarray_umath:

    jupyter notebook导入numpy 失败 报错:Original error was: DLL load failed while importing _multiarray_umath: ...

  9. DataNode 启动失败报错 Incompatible clusterIDs

    文章目录 DataNode 启动失败报错 Incompatible clusterIDs 信息 报错摘要 问题描述 问题原因 分析步骤 解决办法 参考 DataNode 启动失败报错 Incompat ...

最新文章

  1. (视频+图文)机器学习入门系列-第6章 机器学习库Scikit-learn
  2. 互联网分布式架构技术概述
  3. java model 中文乱码,java传值乱码解决方法
  4. 工作332:uni-uview上传获取到对应数据
  5. [Android] Bitmap OOM解决办法一
  6. python查找元素的下标 leetcode 392
  7. ORACLE计算表引占用空间大小
  8. Exchange2010中文版安装教程
  9. 微信小程序订阅消息 微信公众号模板消息
  10. 【NLP】NLP数据标注工具汇总
  11. 详解无刷直流电机的工作原理
  12. 3097-小鑫爱数学
  13. 自学成才秘籍!机器学习深度学习经典资料汇总
  14. 美团外卖前端可视化界面组装平台 —— 乐高
  15. 破解第三课 关键跳和关键CALL
  16. 郭店楚简——原简整理,文物出版社
  17. linux 下动手实现bash -lR 命令
  18. R 多变量数据预处理_R语言数据可视化之数据分布图(直方图、密度曲线、箱线图、等高线、2D密度图)...
  19. 20160411_使用老毛桃破解电脑密码
  20. Istio-PilotDiscovery服务的创建

热门文章

  1. python基因差异分析_差异基因
  2. idea persistence生成_真厉害!竟然可以这样用IDEA通过数据库生成lombok版的POJO...
  3. 华为ap配置_Win10频发蓝屏,深度Deepin系统,调试华为AC和AP
  4. Linux下的NFS搭建配置
  5. Netty详解(四):Netty 整体架构
  6. java 正则匹配 sql星号,18. 正则表达式:开头、结尾、任意一个字符、星号和加号匹配...
  7. 直播平台搭建中你需要注意的小细节
  8. Java stackoverflowerror异常与outofmemoryerror异常区别
  9. oracle树形语句
  10. iOS - UITableViewCell Custom Selection Style Color