Java中Iterator的fast-fail分析
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分析的更多相关文章
- Java中arraylist和linkedlist源代码分析与性能比較
Java中arraylist和linkedlist源代码分析与性能比較 1,简单介绍 在java开发中比較经常使用的数据结构是arraylist和linkedlist,本文主要从源代码角度分析arra ...
- Java中Iterator类的详细介绍
迭代器模式:就是提供一种方法对一个容器对象中的各个元素进行访问,而又不暴露该对象容器的内部细节. 概述 Java集合框架的集合类,我们有时候称之为容器.容器的种类有很多种,比如ArrayList.Li ...
- 大杂烩 -- Java中Iterator的fast-fail分析
基础大杂烩 -- 目录 Java中的Iterator非常方便地为所有的数据源提供了一个统一的数据读取(删除)的接口,但是新手通常在使用的时候容易报如下错误ConcurrentModificationE ...
- Java 中Iterator 、Vector、ArrayList、List 使用深入剖析
标签:Iterator Java List ArrayList Vector 线性表,链表,哈希表是常用的数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构.这些 ...
- Java中Iterator(迭代器)的用法及其背后机制的探究
在Java中遍历List时会用到Java提供的Iterator,Iterator十分好用,原因是: 迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结 ...
- Java中String连接性能的分析【转】
[转]http://www.blogjava.net/javagrass/archive/2010/01/24/310650.html 总结:如果String的数量小于4(不含4),使用String. ...
- Java中Iterator(迭代器)实现原理
在Java中遍历List时会用到Java提供的Iterator,Iterator十分好用,原因是: 迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结 ...
- Java中String连接性能的分析
总结:如果String的数量小于4(不含4),使用String.concat()来连接String,否则首先计算最终结果的长度,再用该长度来创建一个StringBuilder,最后使用这个String ...
- Java中Iterator用法整理
迭代器(Iterator) 迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构.迭代器通常被称为“轻量级”对象,因为创建它的代价小. Java中的I ...
随机推荐
- 【GLSL教程】(一)图形流水线 【转】
http://blog.csdn.net/racehorse/article/details/6593719 这是一些列来自lighthouse3d的GLSL教程,非常适合入门.我将边学习边翻译该教程 ...
- 由内省引出JavaBean的应用
IntroSpector-->javaBean-->特殊的java类 get和set方法 ReflectPoint pt1 = new ReflectPoint(3,5); String ...
- maven dubbo zookeeper 项目搭建(有效)jar包非war测试
zookeeper安装以及dubbo-admin.war(管理端)配置启动,本章省略,参考其他内容 这里主要说服务提供者和消费者 项目结构: 1)服务端 DemoServer.java package ...
- 移动端开发者福利-免费收费api收藏
一 .api 1.https://www.juhe.cn/ 跟百度api集市差不多,超级赞,做好认证就行了,我有20+认证能用的免费api 2.http://apistore.baidu.com/as ...
- 阿里巴巴天猫超市团队招聘java开发工程师
大家好,发个招聘信息:我是阿里巴巴集天猫超市开发团队的同学,我们部门目前在杭州招人,P6岗位,要求至少本科,熟悉java,spring等java开发技术,最好有互联网企业开发经验,感兴趣的可以通过我直 ...
- SharePoint 2013 和 SharePoint 2010 功能对比
微软的网站找东西太麻烦了,在这记录一下. 从 SharePoint 2010 更改为 SharePoint 2013 http://technet.microsoft.com/zh-cn/librar ...
- 移动应用开发测试工具Bugtags集成和使用教程【转载】
前段时间,有很多APP突然走红,最终却都是樱花一现.作为一个创业团队,突然爆红是非常难得的机会.然并卵,由于没有经过充分的测试,再加上用户的激增,APP闪退.服务器数据异常等问题就被暴露出来,用户的流 ...
- Mysql 5.7.18 加密连接mysql_ssl_rsa_setup
MySQL 5.7.18 下载地址: https://dev.mysql.com/get/Downloads/MySQL-5.7/mysql-5.7.18-linux-glibc2.5-x86_64. ...
- golang 格式化时间成datetime
Golang或者Beego,总需要往数据库里写datetime时间戳. Golang对时间格式支持并不理想. 先看一个例子: package main import ( "fmt" ...
- 数据库ACID操作---事务四原则
事务操作四原则: 1>原子性:简单来说——整个事务操作如同原子已经是物理上最小的单位,不可分离事务操作要么一起成功,要么一起失败. 2>一致性:倘若事务操作失败,则回滚事务时,与原始状态一 ...