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

最新文章

  1. EditPlus 3.7 中文版已经发布
  2. php.ini 配置详细选项
  3. JS, Node.js, npm简介
  4. Leetcode 290 Word Pattern STL
  5. svn 终端命令
  6. java中4中类修饰符访问范围
  7. HTML5 离线缓存忽略主页实例
  8. golang实现tcp接入服务器
  9. 【视频】零基础学Android开发:蓝牙聊天室APP(四)
  10. TOGAF架构内容框架之内容元模型(下)
  11. 【Harmony】概述
  12. [1] 插件架构(PLUG-IN)
  13. margin负值的相关应用
  14. 【BZOJ2186】沙拉公主的困惑(数论)
  15. [LeetCode] Base 7 基数七
  16. 页面的div中有滚动条,js实现刷新页面后回到记录时滚动条的位置
  17. eregi
  18. Java判断一个字符是否是数字的几种方法的代码
  19. (网页)JS中的小技巧,但十分的实用!
  20. vim 支持 nginx配置文件 语法高亮

热门文章

  1. 聊聊、Zookeeper Linux 启动
  2. iOS进行单元测试OCUnit+xctool
  3. 【spring boot】8.spring boot的日志框架logback使用
  4. hdu1017(C++)
  5. nx sdk
  6. 【Salvation】—— 项目策划&amp;市场分析
  7. linux中at命令
  8. 利用AFNetworking框架去管理从聚合数据上面请求到的数据
  9. linux过滤ip段
  10. bigAutocomplete实现联想