1、fail-fast简介

fail-fast机制是java集合(Collection)中的一个错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。

例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

2、fail-fast实例

package com.collection.list;

import java.util.*;
import java.util.concurrent.*; /*
* @desc java集合中Fast-Fail的测试程序。
*
* fast-fail事件产生的条件:当多个线程对Collection进行操作时,若其中某一个线程通过iterator去遍历集合时,该集合的内容被其他线程所改变;则会抛出ConcurrentModificationException异常。
* fast-fail解决办法:通过util.concurrent集合包下的相应类去处理,则不会产生fast-fail事件。
*
* 本例中,分别测试ArrayList和CopyOnWriteArrayList这两种情况。ArrayList会产生fast-fail事件,而CopyOnWriteArrayList不会产生fast-fail事件。
* (01) 使用ArrayList时,会产生fast-fail事件,抛出ConcurrentModificationException异常;定义如下:
* private static List<String> list = new ArrayList<String>();
* (02) 使用时CopyOnWriteArrayList,不会产生fast-fail事件;定义如下:
* private static List<String> list = new CopyOnWriteArrayList<String>();
*
* @author skywang
*/
public class FastFailTest { private static List<String> list = new ArrayList<String>();
//private static List<String> list = new CopyOnWriteArrayList<String>();
public static void main(String[] args) { // 同时启动两个线程对list进行操作!
new Thread(new ThreadOne()).start();
new Thread(new ThreadTwo()).start();
} private static void printAll() {
System.out.println(""); String value = null;
Iterator iter = list.iterator();
while(iter.hasNext()) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
value = (String)iter.next();
System.out.print(value+", ");
}
} /**
* 向list中依次添加0,1,2,3,4,5,每添加一个数之后,就通过printAll()遍历整个list
*/
private static class ThreadOne implements Runnable { public void run() {
System.out.println("Thread One");
int i = 0;
while (i<6) {
list.add(String.valueOf(i));
printAll();
i++; }
}
} /**
* 向list中依次添加10,11,12,13,14,15,每添加一个数之后,就通过printAll()遍历整个list
*/
private static class ThreadTwo implements Runnable { public void run() {
System.out.println("Thread Two'");
int i = 10;
while (i<16) {
list.add(String.valueOf(i));
printAll();
i++;
}
}
} }

运行结果

0, Exception in thread "Thread-0" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at com.collection.list.FastFailTest.printAll(FastFailTest.java:42)
at com.collection.list.FastFailTest.access$300(FastFailTest.java:20)
at com.collection.list.FastFailTest$ThreadOne.run(FastFailTest.java:57)
at java.lang.Thread.run(Thread.java:745)

结果说明:1. 启动两个线程在添加一个元素后,直接进行全局遍历 2. 当一个线程在遍历list的时候,list的内容被另外一个线程改变了,就会抛出CurrentModificationException异常,产生fail-fast事件

3、fail-fast的解决办法

fail-fast机制,是一种错误检测机制。它只能被用来检测错误,因为JDK并不保证fail-fast机制一定会发生。若在多线程环境下使用fail-fast机制的集合,建议使用“java.util.concurrent包下的类”去取代“java.util包下的类”。
所以,本例中只需要将ArrayList替换成java.util.concurrent包下对应的类即可。
即,将代码

private static List<String> list = new ArrayList<String>();

替换为

private static List<String> list = new CopyOnWriteArrayList<String>();

则可以解决该办法。

4、fail-fast原理

产生fail-fast事件,是通过抛出ConcurrentModificationException异常来触发的。
那么,ArrayList是如何抛出ConcurrentModificationException异常的呢?

我们知道,ConcurrentModificationException是在操作Iterator时抛出的异常。我们先看看Iterator的源码。ArrayList的Iterator是在父类AbstractList.java中实现的。代码如下:

 

从中,我们可以发现在调用 next() 和 remove()时,都会执行 checkForComodification()。若 “modCount 不等于 expectedModCount”,则抛出ConcurrentModificationException异常,产生fail-fast事件。

要搞明白 fail-fast机制,我们就要需要理解什么时候“modCount 不等于 expectedModCount”!
从Itr类中,我们知道 expectedModCount 在创建Itr对象时,被赋值为 modCount。通过Itr,我们知道:expectedModCount不可能被修改为不等于 modCount。所以,需要考证的就是modCount何时会被修改。

接下来,我们查看ArrayList的源码,来看看modCount是如何被修改的。

 

从中,我们发现:无论是add()、remove(),还是clear(),只要涉及到修改集合中的元素个数时,都会改变modCount的值。

接下来,我们再系统的梳理一下fail-fast是怎么产生的。步骤如下:
(01) 新建了一个ArrayList,名称为arrayList。
(02) 向arrayList中添加内容。
(03) 新建一个“线程a”,并在“线程a”中通过Iterator反复的读取arrayList的值
(04) 新建一个“线程b”,在“线程b”中删除arrayList中的一个“节点A”。
(05) 这时,就会产生有趣的事件了。
       在某一时刻,“线程a”创建了arrayList的Iterator。此时“节点A”仍然存在于arrayList中,创建arrayList时,expectedModCount = modCount(假设它们此时的值为N)。
       在“线程a”在遍历arrayList过程中的某一时刻,“线程b”执行了,并且“线程b”删除了arrayList中的“节点A”。“线程b”执行remove()进行删除操作时,在remove()中执行了“modCount++”,此时modCount变成了N+1
“线程a”接着遍历,当它执行到next()函数时,调用checkForComodification()比较“expectedModCount”和“modCount”的大小;而“expectedModCount=N”,“modCount=N+1”,这样,便抛出ConcurrentModificationException异常,产生fail-fast事件。

至此,我们就完全了解了fail-fast是如何产生的!
即,当多个线程对同一个集合进行操作的时候,某线程访问集合的过程中,该集合的内容被其他线程所改变(即其它线程通过add、remove、clear等方法,改变了modCount的值);这时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

5、解决fail-fast原理

上面,说明了“解决fail-fast机制的办法”,也知道了“fail-fast产生的根本原因”。接下来,我们再进一步谈谈java.util.concurrent包中是如何解决fail-fast事件的。
还是以和ArrayList对应的CopyOnWriteArrayList进行说明。我们先看看CopyOnWriteArrayList的源码:

 

从中,我们可以看出:

(01) 和ArrayList继承于AbstractList不同,CopyOnWriteArrayList没有继承于AbstractList,它仅仅只是实现了List接口。
(02) ArrayList的iterator()函数返回的Iterator是在AbstractList中实现的;而CopyOnWriteArrayList是自己实现Iterator。
(03) ArrayList的Iterator实现类中调用next()时,会“调用checkForComodification()比较‘expectedModCount’和‘modCount’的大小”;但是,CopyOnWriteArrayList的Iterator实现类中,没有所谓的checkForComodification(),更不会抛出ConcurrentModificationException异常!

(04) CopyOnWriteArrayList, 也没有用线程同步,只是将原数组在遍历的时候先将原数组拷贝一份,因此数组再变化时不会影响正在遍历的数据

(05) 解决fail-fast 也可以用线程同步方案,通知只能有一个线程访问数组,但是这样会降低访问的效率

此文参考:http://www.cnblogs.com/skywang12345/p/3308762.html

Java中Iterator的fast-fail分析的更多相关文章

  1. Java中arraylist和linkedlist源代码分析与性能比較

    Java中arraylist和linkedlist源代码分析与性能比較 1,简单介绍 在java开发中比較经常使用的数据结构是arraylist和linkedlist,本文主要从源代码角度分析arra ...

  2. Java中Iterator类的详细介绍

    迭代器模式:就是提供一种方法对一个容器对象中的各个元素进行访问,而又不暴露该对象容器的内部细节. 概述 Java集合框架的集合类,我们有时候称之为容器.容器的种类有很多种,比如ArrayList.Li ...

  3. 大杂烩 -- Java中Iterator的fast-fail分析

    基础大杂烩 -- 目录 Java中的Iterator非常方便地为所有的数据源提供了一个统一的数据读取(删除)的接口,但是新手通常在使用的时候容易报如下错误ConcurrentModificationE ...

  4. Java 中Iterator 、Vector、ArrayList、List 使用深入剖析

    标签:Iterator Java List ArrayList Vector 线性表,链表,哈希表是常用的数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构.这些 ...

  5. Java中Iterator(迭代器)的用法及其背后机制的探究

    在Java中遍历List时会用到Java提供的Iterator,Iterator十分好用,原因是: 迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结 ...

  6. Java中String连接性能的分析【转】

    [转]http://www.blogjava.net/javagrass/archive/2010/01/24/310650.html 总结:如果String的数量小于4(不含4),使用String. ...

  7. Java中Iterator(迭代器)实现原理

    在Java中遍历List时会用到Java提供的Iterator,Iterator十分好用,原因是: 迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结 ...

  8. Java中String连接性能的分析

    总结:如果String的数量小于4(不含4),使用String.concat()来连接String,否则首先计算最终结果的长度,再用该长度来创建一个StringBuilder,最后使用这个String ...

  9. Java中Iterator用法整理

    迭代器(Iterator) 迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构.迭代器通常被称为“轻量级”对象,因为创建它的代价小. Java中的I ...

随机推荐

  1. Delphi Integer 转成单字节

    整形不能超过256 b:=Byte(StrToInt(n)); var   s: string;   b: Byte; begin   s := Edit1.Text;   b := Byte(Str ...

  2. JSON API:用 JSON 构建 API 的标准指南中文版

    译文地址:https://github.com/justjavac/json-api-zh_CN 假设你和你的团队以前争论过使用什么方式构建合理 JSON 响应格式, 那么 JSON API 就是你的 ...

  3. 通达OA 小飞鱼工作流在线培训教程(七)工作流应用的意义及基础设置(图文)

    这个课程计划已经有一段时间了,经过这段时间结合实际网络教学又进行了一些总结,这里将陆续为大家呈现相关的工作流设计开发课程. 同一时候线上的视频教学课程也将立即上线,欢迎朋友们的关注. 首先介绍一些工作 ...

  4. 【前端阅读】——《程序员思维修炼》摘记&读后感&思维导图

    前言:这是一本介绍如何用脑的书,并从思维的角度(以程序员为例),介绍如何从新手成为专家.作者带领着读者(我)共同经历一次有关认知科学.神经学.学习和行为理论的旅程,探索人类大脑令人 惊奇的工作的机制, ...

  5. AutoCAD如何批量设置线宽

    1 如下图所示,全部选中图形,然后设置线宽   2 但是一般剖面线并不需要这么宽,我们打印预览可以发现完全黑的看不清了.   3 把线宽都设为0.2毫米效果不错

  6. Sereja and Array-数组操作或者线段树或树状数组

    CodeForces - 315B Sereja and Array Time Limit: 1000MS   Memory Limit: 262144KB   64bit IO Format: %I ...

  7. 代理server的概要知识

    技术支持请留言:http://www.lcpower.cn 一.什么是代理server? 代理server英文全称是Proxy Server.其功能就是代理网络用户去取得网络信息.形象的说:它是网络信 ...

  8. php的strip_tags,htmlspecialchars,htmlentities,stripslashes,addslashes解释

    php函数蛮多的,要完整的每个函数都理解深刻是个挺有挑战性的事情. strip_tags,htmlspecialchars,htmlentities,stripslashes,addslashes这几 ...

  9. wpf SplitButton

     SplitButton该控件除了本身Button 的功能外,还具有下拉菜单的功能,能够在按键右側加入下拉菜单控件: <SplitButton Content="..." ...

  10. HTML5之Canvas绘图(二) ——应用篇之七巧板

    1.canvas绘制七巧板-- <!DOCTYPE html> <html> <head> <meta charset="UTF-8"&g ...