一、问题描述

话不多说,先上代码:

    public static void main(String[] args) throws InterruptedException {
List<String> list = new ArrayList<String>();
     list.add("第零个");
list.add("第一个");
list.add("第二个");
list.add("第三个");
list.add("第四个"); for (String str : list) {
if (str.equals("第三个")) {
System.out.println("删除:" + str);
list.remove(str);
}
}
System.out.println(list);
}

知道快速失败机制的可能都会说,不能在foreach循环里用集合直接删除,应该使用iterator的remove()方法,否则会报错:java.util.ConcurrentModificationException

但是这个代码的真实输出结果却是:

并没有报错,这是为什么呢?

二、基础知识

java的foreach 和 快速失败机制还是先简单介绍一下:

foreach过程:

Java在通过foreach遍历集合列表时,会先为列表创建对应的迭代器,并通过调用迭代器的hasNext()函数判断是否含下一个元素,若有则调用iterator.next()获取继续遍历,没有则结束遍历。

快速失败机制:

因为非线程安全,迭代器的next()方法调用时会判断modCount==expectedModCount,否则抛出ConcurrentModIficationException。modCount只要元素数量变化(add,remove)就+1,而集合和表的add和remove方法却不会更新expectedModCount,只有迭代器remove会重置expectedModCount=modCount,并将cursor往前一位。所以在使用迭代器循环的时候,只能使用迭代器的修改。

三、分析

所以由上面的foreach介绍我们可以知道上面的foreach循环代码可以写成如下形式:

        for (Iterator iterator = list.iterator(); iterator.hasNext();) {
String str = (String) iterator.next();
if (str.equals("第三个")) {
System.out.println("删除:" + str);
list.remove(str);
}
}

重点就在于 iterator.next() 这里,我们看看next()的源码:【此处的迭代器是ArrayList的私有类,实现了迭代器接口Iterator,重写了各种方法】

         public E next() {
checkForComodification();
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}

注意到第7行!,也就是说,cursor最开始是 i,调用next()后就将第 i 个元素返回,然后变成下一个元素的下标了,所以在遍历到倒数第二个元素的时候cursor已经为最后一个元素的下标(size-1)了,

注意了!在调用集合或者.remove(int)的方法时,并不会对cursor进行改变,【具体操作:将元素删除后,调用System.arraycopy让后面的的元素往前移动一位,并将最后一个元素位释放】,而本程序中此时的size变成了原来的size-1=4,而cursor还是原来的size-1=4,二者相等!,再看看判定hasNext():

        public boolean hasNext() {
return cursor != size();
}

此时cursor==size()==4,程序以为此时已经遍历完毕,所以根本不会进入循环中,也就是说根本不会进入到next()方法里,也就不会有checkForComodification() 的判断。

 四、验证

我们在程序中foreach循环的第一句获取str后面加入一个打印,  System.out.println(str); ,

这样每次进入foreach循环就会打印1,其他不变,我们再来运行一次,结果如下:

显然,第四个元素没有被遍历到,分析正确,那假如使用iterator.remove()呢?

那我们再来看看iterator.remove()的源码,【此处的iterator是ArrayList的私有类,实现了迭代器接口Iterator,重写了各种方法】

         public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification(); try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}

看第7行,内部其实也是调用的所属list的remove(int)方法,但是不同的地方要注意了:

第9行:将cursor--,也就是说在删除当前元素后,他又把移动后的指针放回了当前元素下标,所以继续循环不会跳过当前元素位的新值;

第11行:expectedModCount = modCount; 更新expectedModCount,使二者相同,在继续循环中不会被checkForComodification()检测出报错。

五、结论

1. 每次foreach循环开始时next()方法会使cursor变为下一个元素下标;

2. ArrayList的remove()方法执行完后,下一个元素移动到被删除元素位置上,由1可知,cursor此时指向原来被删除元素的下一个的下一个元素所在位置,此时继续foreach循环,被删除元素的下一个元素不会被遍历到;

3. checkForComodification()方法用来实现快速失败机制的判断,此方法在iterator.next()方法中,必须在进入foreach循环后才会被调用;

4. 由2,当ArrayList的remove()方法在foreach删除倒数第二个元素时,继续foreach循环,倒数第一个元素会被跳过,从而退出循环,联合3可知,在删除倒数第二个元素后,并不会进入快速失败机制的判断。

5. iterator.remove()方法会在删除和移动元素后将cursor放回正确的位置,不会导致元素跳过,并且更新expectedModCount,是一个安全的选择。

关于java中ArrayList的快速失败机制的漏洞——使用迭代器循环时删除倒数第二个元素不会报错的更多相关文章

  1. java中的fail-fast(快速失败)机制

    java中的fail-fast(快速失败)机制 简介 fail-fast机制,即快速失败机制,是java集合中的一种错误检测机制.当在迭代集合的过程中对该集合的结构改变是,就有可能会发生fail-fa ...

  2. ArrayList之foreach循环删除倒数第二个元素,不触发fail-fast机制

    今天一朋友问了个问题,对于如下一段代码,运行后会有怎样的结果? public class ArrayListTest { public static void main(String[] args) ...

  3. ArrayList在foreach删除倒数第二个元素不抛并发修改异常的问题

    平时我们使用ArrayList比较多,但是我们是否知道ArrayList在进行foreach的时候不能直接通过list的add或者move方法进行删除呢, 原因就是在我们进行foreach遍历的时候, ...

  4. 快速失败机制--fail-fast

    fail-fast 机制是Java集合(Collection)中的一种错误机制.当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast(快速失败)事件.例如:当某一个线程A通过iter ...

  5. 【原创】快速失败机制&失败安全机制

    这是why技术的第29篇原创文章 之前在写<这道Java基础题真的有坑!我求求你,认真思考后再回答.>这篇文章时,我在8.1小节提到了快速失败和失败安全机制. 但是我发现当我搜索" ...

  6. java中ArrayList 、LinkList区别

    转自:http://blog.csdn.net/wuchuanpingstone/article/details/6678653 个人建议:以下这篇文章,是从例子说明的方式,解释ArrayList.L ...

  7. Java中ArrayList的删除元素总结

    Java中循环遍历元素,一般有for循环遍历,foreach循环遍历,iterator遍历. 先定义一个List对象 List<String> list = new ArrayList&l ...

  8. windows系统的快速失败机制---fastfail

    windows系统的快速失败机制---fastfail,是一种用于“快速失败”请求的机制 — 一种潜在破坏进程请求立即终止进程的方法. 无法使用常规异常处理设施处理可能已破坏程序状态和堆栈至无法恢复的 ...

  9. JAVA中ArrayList用法

    JAVA中ArrayList用法 2011-07-20 15:02:03|  分类: 计算机专业 |  标签:java  arraylist用法  |举报|字号 订阅     Java学习过程中做题时 ...

随机推荐

  1. 关于Properties的用法的详细解释

    如果不熟悉 java.util.Properties类,那么现在告诉您它是用来在一个文件中存储键-值对的,其中键和值是用等号分隔的.(如清单 1 所示).最近更新的java.util.Properti ...

  2. linux中增加swap分区文件的步骤方法

     一.swap交换分区 Swap分区在系统的物理内存不够用的时候,把硬盘空间中的一部分空间释放出来,以供当前运行的程序使用.那些被释放的空间可能来自一些很长时间没有什么操作的程序,这些被释放的空间被临 ...

  3. 玩转Javascript this用法

    在web项目中Javascript是一门必须要掌握的动态语言,基于Javascript的框架大多离不开不了最基础的Javascript的用法和原理.本文主要是总结一下Javascript中那万恶的th ...

  4. gcc windows版本

    MingW 分 32位和64位版本:下载地址分别如下: http://sourceforge.net/projects/mingw/ http://sourceforge.net/projects/m ...

  5. 设计模式之——浅谈strategy模式(策略模式)

    strategy模式,即策略模式.个人觉得吧,策略模式更多的是一种思维方式. 首先我们要知道,为什么需要策略模式.举个例子,比如用程序输出今天下午去玩什么. PlayGame 玩游戏 package ...

  6. 从WW中剥离一个三维场景框架

    从WW中剥离一个三维场景框架,初步实现的一个.可以绘制一个三角形,但是不能够控制摄像机,没有增加鼠标事件.没有投影,世界变幻之类的东西.以后会不断学习逐步增加进来. 下载地址 下载V1.0.0.2

  7. altium designer生成gerber步骤

    什么是gerber文件             Gerber文件是所有电路设计软件都可以产生的文件,在电子组装行业又称为模版文件(stencil data),在PCB制造业又称为光绘文件.可以说Ger ...

  8. thinkphp5使用PHPMailler发送邮件

    http://www.dawnfly.cn/article-1-350.html 想要了解thinkphp3.2版本发送邮件的,请点击此链接:http://www.dawnfly.cn/article ...

  9. WebDriver API 实例详解(一)

    一.访问某网页地址 被测试网页的网址: http://www.baidu.com Java语言版本的API实例代码: 方法1: package test; import org.testng.anno ...

  10. 史上最全的MonkeyRunner自动化测试从入门到精通(1)

    原文地址https://zhuanlan.zhihu.com/p/26043620 MonkeyRunner使用 #-*- coding:utf-8 –*- from com.android.monk ...