前言:最近看 java 集合方面的源码,了解到集合使用了 fail-fast 的机制,这里就记录一下这个机制是什么,有什么用,如何实现的。

一、fail-fast 简介

  fail-fast 机制,即快速失败机制,是java集合(Collection)中的一种错误检测机制。当在迭代集合的过程中该集合在结构上发生改变的时候,就有可能会发生 fail-fast,即抛出 ConcurrentModificationException 异常。fail-fast 机制并不保证在不同步的修改下一定会抛出异常,它只是尽最大努力去抛出,所以这种机制一般仅用于检测 bug。  

  fail-fast 机制出现在 java 集合的 ArrayList、HashMap 等,在多线程或者单线程里面都有可能出现快速报错,即出现 ConcurrentModificationException 异常。

二、fail-fast 有什么用?

  为了检测在迭代集合的过程中,这个集合是否发生了增加、删除等(add、remove、clear)使结构发生变化的事。

三、测试

  这个单测跑下来是成功的,下面有讲解为什么。

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test; import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List; /**
* @author yule
* @date 2018/9/13 17:05
*/
public class ArrayListTest {
private List<Integer> list = null; @Before
public void buildList(){
list = new ArrayList<>();
for(int i = 0; i < 10; i++){
list.add(i);
}
Assert.assertEquals("[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]", list.toString());
} @Test
public void testFor(){
for(int i = 0; i < list.size(); i++){
if(i == 2){
//这个会删除成功,这里没有 fail-fast 的机制
list.remove(2);
}
}
Assert.assertEquals("[0, 1, 3, 4, 5, 6, 7, 8, 9]", list.toString());
} @Test(expected = ConcurrentModificationException.class)
public void testForEach(){
for(int x : list){
if(x == 2){
//这个抛出异常,这里就是 fail-fast 的机制,毕竟 foreach 底层就是 Iterator
list.remove(2);
}
}
} @Test(expected = ConcurrentModificationException.class)
public void testIterator(){
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
int x = iterator.next();
if(x == 2){
//这个抛出异常,这里就是 fail-fast 的机制
list.remove(2);
}
}
} @Test
public void testIteratorSuccess(){
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
int x = iterator.next();
if(x == 2){
//这个会成功,不会抛出异常,这里也是 fail-fast 的机制,不知道这里为什么会成功,可以继续看下去
iterator.remove();
}
}  
     Assert.assertEquals("[0, 1, 3, 4, 5, 6, 7, 8, 9]", list.toString());
  }
}

  第一个 testFor() 单测成功,是因为普通的 for 循环没有 fail-fast 机制,因为 fail-fast 机制只针对迭代集合的过程。

  第二个 testForEach() 单测抛异常,是因为 forEach 底层就是使用了迭代器,其原因和 testIterator() 单测一致。

  第三个 testIterator() 单测抛异常,是因为 ArrayList.remove() 方法只修改了 modCount++,而没有修改 Itr 的 expectedModCount。(详见下面原理)

  第四个 testIteratorSuccess() 单测成功,是因为 ArrayList 的迭代器的 remove() 方法不仅仅是修改了 modCount,也修改了 Itr 的 expectedModCount。(详见下面原理)

四、fail-fast 在源码中(ArrayList)如何实现的?(原理)

  首先,必须了解 fail-fast 两个关键的东西,ArrayList 的 modCount 和 ArrayList.Itr 内部类的 expectedModCount。

  其中,modCount 是抽象类 AbstractList 中的变量,默认为 0,而 ArrayList 继承了 AbstractList ,所以也有这个变量,modCount 用于记录集合操作过程中作的修改次数,并不是 size。每次 add 或者 remove modCount 都会 ++。

  其中,expectedModCount 是 ArrayList 内部类 Itr 的成员变量,初始值为 modCount。执行迭代器的 remove、add 方法,都会先执行 ArrayList 的 remove、add 方法(modCount++),然后会执行 expectedModCount = modCount。

  上面说到 fail-fast 只针对 迭代器,所以需要知道 ArrayList 的迭代器的实现源码:

    /**
* Returns an iterator over the elements in this list in proper sequence.
*
* <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
*
* @return an iterator over the elements in this list in proper sequence
*/
public Iterator<E> iterator() {
return new Itr();
}

  ArrayList 的 内部类 Itr 实现了 Iterator 接口,源码如下:

/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
    // 指集合遍历过程中的即将遍历的元素的索引
int cursor; // index of next element to return  
int lastRet = -1; // index of last element returned; -1 if no such
// 这个就是 fail-fast 判断的关键变量,初始值就为ArrayList中的modCount
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();
}
} @Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
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
cursor = i;
lastRet = i - 1;
checkForComodification();
} final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}

  其中,cursor 是指集合遍历过程中的即将遍历的元素的索引。

  lastRet 是 cursor  - 1,默认为 -1,即不存在上一个时,为 -1,它主要用于记录刚刚遍历过的元素的索引。

  迭代器迭代结束的标志就是 hasNext() 返回 false,而该方法就是用 cursor 游标和 size (集合中的元素数目)进行对比,当 cursor 等于 size 时,表示已经遍历完成。

五、解惑

1、什么是指集合在结构上发生变化呢?

  其实就是指 size 有变动,比如 ArrayList 的 add 和 delete、clear。

2、迭代集合的过程又是指什么呢?(这里涉及到 forEach 的原理)

  迭代集合的过程指的就是使用代迭代器 Iterator 或者 forEach 语法,实际上一个类要使用 forEach 就必须实现 Iterable 接口并且重写它的 Iterator 方法所以 forEach 在本质上还是使用的 Iterator。因为 forEach 原理:在编译的时候编译器会自动将对 for 这个关键字的使用转化为对目标的迭代器的使用。

  所以得出结论:

  1、ArrayList 之所以能使用 foreach 循环遍历,是因为 ArrayList 所有的 List 都是 Collection 的子接口,而 Collection 是 Iterable 的子接口,ArrayList 的父类 AbstractList 正确地实现了 Iterable 接口的 iterator 方法。

  2、任何一个集合,无论是 JDK 提供的还是自己写的,只要想使用 foreach 循环遍历,就必须正确地实现 Iterable 接口

  实际上,这种做法就是23中设计模式中的迭代器模式

3、那么又有一个问题:数组并没有实现 Iterable 接口啊,为什么数组也可以用 foreach 循环遍历呢?

  其实:Java 将对于数组的 foreach 循环转换为对于这个数组每一个的循环引用。

  所以结论为:编译器对集合的 forEach 会调用集合的 迭代器;对数组的 forEach 会调用数组的 for 循环

  测试:java文件

  编译后:class 文件

  

参考:https://blog.csdn.net/zymx14/article/details/78394464

Java集合框架——容器的快速报错机制 fail-fast 是什么?的更多相关文章

  1. java集合框架容器 java框架层级 继承图结构 集合框架的抽象类 集合框架主要实现类

    本文关键词: java集合框架  框架设计理念  容器 继承层级结构 继承图 集合框架中的抽象类  主要的实现类 实现类特性   集合框架分类 集合框架并发包 并发实现类 什么是容器? 由一个或多个确 ...

  2. Java集合框架——jdk 1.8 ArrayList 源码解析

    前言:作为菜鸟,需要经常回头巩固一下基础知识,今天看看 jdk 1.8 的源码,这里记录 ArrayList 的实现. 一.简介 ArrayList 是有序的集合: 底层采用数组实现对数据的增删查改: ...

  3. 从上面的集合框架图可以看到,Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射

    从上面的集合框架图可以看到,Java 集合框架主要包括两种类型的容器,一种是集合(Collection),存储一个元素集合,另一种是图(Map),存储键/值对映射.Collection 接口又有 3 ...

  4. Java集合框架梳理(含经典面试题)

    Java Collections Framework是Java提供的对集合进行定义,操作,和管理的包含一组接口,类的体系结构. 1. 整体框架 Java容器类库一共有两种主要类型:Collection ...

  5. [转载] Java集合框架之小结

    转载自http://jiangzhengjun.iteye.com/blog/553191 1.Java容器类库的简化图,下面是集合类库更加完备的图.包括抽象类和遗留构件(不包括Queue的实现): ...

  6. 【集合系列】- 初探java集合框架图

    一.集合类简介 Java集合就像一种容器,可以把多个对象(实际上是对象的引用,但习惯上都称对象)"丢进"该容器中.从Java 5 增加了泛型以后,Java集合可以记住容器中对象的数 ...

  7. 【JAVA集合框架之List】

    一.List接口概述. List有个很大的特点就是可以操作角标. 下面开始介绍List接口中相对于Collection接口比较特别的方法.在Collection接口中已经介绍的方法此处就不再赘述. 1 ...

  8. 浅入深出之Java集合框架(上)

    Java中的集合框架(上) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,如果已经有java基础的小伙伴可以直接跳到<浅入深出之Java集合框架 ...

  9. 浅入深出之Java集合框架(下)

    Java中的集合框架(下) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,哈哈这篇其实也还是基础,惊不惊喜意不意外 ̄▽ ̄ 写文真的好累,懒得写了.. ...

随机推荐

  1. POJ3422费用流

    Description: 一个N * N的奖赏地图,你可以走k次这个地图,但是每一次你走过一个有分的节点,你获得得分,但这个节点的得分都要清零,问你走k次地图的最大得分 Solution: 把得分变成 ...

  2. LeNet,AlexNet,GoogleLeNet,VggNet等网络对比

    CNN的发展史 上一篇回顾讲的是2006年Hinton他们的Science Paper,当时提到,2006年虽然Deep Learning的概念被提出来了,但是学术界的大家还是表示不服.当时有流传的段 ...

  3. 一些有价值的Blog推荐

    待看的一些文章 1. 性能调优攻略 http://coolshell.cn/articles/7490.html 2. 内存的存储管理--段式和页式管理的区别 http://blog.sina.com ...

  4. Linux pwn入门教程(2)——shellcode的使用,原理与变形

    作者:Tangerine@SAINTSEC 0×00 shellcode的使用 在上一篇文章中我们学习了怎么使用栈溢出劫持程序的执行流程.为了减少难度,演示和作业题程序里都带有很明显的后门.然而在现实 ...

  5. 《你不知道的javascript》读书笔记2

    概述 放假读完了<你不知道的javascript>上篇,学到了很多东西,记录下来,供以后开发时参考,相信对其他人也有用. 这篇笔记是这本书的下半部分,上半部分请见<你不知道的java ...

  6. itext7知识点研究(PDF编辑)

    取出pdf文档文字 String sourceFolder2 = "E:\\picture2\\租赁合同2.pdf"; PdfDocument doc = new PdfDocum ...

  7. Ajax 的学习

    (一)基础知识和新的概念      1,AJAX 就是浏览器提供的一套 API,可以通过 JavaScript 调用,从而实现通过代码控制请求与响应.实现 网络编程.   2,AJAX(Asynchr ...

  8. Testing - 软件测试知识梳理 - 基础概念

    测试是为了度量和提高被测试软件的质量,对测试软件进行工程设计.实施.维护的的整个生命周期过程. 仅仅发现Bug是测试的初步,而分析出根本原因推动问题的解决,却要有很深的功底. 不同的测试岗位从事不同的 ...

  9. Jenkins和gitlab集成自动构建

    Jenkins安装插件 Jenkins上需要安装如下插件 Gitlab Hook Plugin,GitLab Plugin Job配置 在需要自动触发的Job中 选择Build Triggers进行如 ...

  10. Unity 处理预设中的中文

    Unity 处理预设中的中文 需求由来 项目接入越南版本 需要解决的文本问题 获取UI预设Label里面的中文(没被代码控制)提供给越南 Label里面的中文替换成越南文 解决流程 迭代获取Asset ...