以下内容基于jdk1.7.0_79源码;

什么是集合迭代器快速失败行为

以ArrayList为例,在多线程并发情况下,如果有一个线程在修改ArrayList集合的结构(插入、移除...),而另一个线程正在用迭代器遍历读取集合中的元素,此时将抛出ConcurrentModificationException异常立即停止迭代遍历操作,而不需要等到遍历结束后再检查有没有出现问题;

ArrayList.Itr迭代器快速失败源码及例子

查看ArrayList的Itr迭代器源码,可以看到Itr为ArrayList的私有内部类,有一个expectedModCount成员属性,在迭代器对象创建的时候初始化为ArrayList的modCount,即当迭代器对象创建的时候,会将集合修改次数modCount存到expectedModCount里,然后每次遍历取值的时候,都会拿ArrayList集合修改次数modCount与迭代器的expectedModCount比较,如果发生改变,说明集合结构在创建该迭代器后已经发生了改变,直接抛出ConcurrentModificationException异常,如下代码;

        final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}

先举一个不是多线程的简单例子,在创建迭代器后,往ArrayList插入一条数据,然后利用迭代器遍历,如下代码,将抛出ConcurrentModificationException异常:

package com.pichen.basis.col;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; public class Main { public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
for(int i = 0; i < 10; i++){
list.add(i);
} Iterator<Integer> iterator = list.iterator();
list.add(10);
while(iterator.hasNext()){
iterator.next();
}
}
}
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at com.pichen.basis.col.Main.main(Main.java:18)

再来个多线程的例子,我们创建一个t1线程,循环往集合插入数据,另外主线程获取集合迭代器遍历集合,如下代码,在遍历的过程中将抛出ConcurrentModificationException异常:

package com.pichen.basis.col;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; class ThreadTest implements Runnable{
private List<Integer> list;
public ThreadTest(List<Integer> list) {
this.list = list;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.list.add(10);
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
List<Integer> list = new ArrayList<Integer>();
for(int i = 0; i < 10; i++){
list.add(i);
} ThreadTest t1 = new ThreadTest(list);
new Thread(t1).start(); Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
System.out.print(iterator.next() + " ");
Thread.sleep(80);
}
}
}

结果打印:

0 1 2 3 4 5 6 Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
at java.util.ArrayList$Itr.next(ArrayList.java:831)
at com.pichen.basis.col.Main.main(Main.java:44)

ConcurrentModificationException异常解决办法

对ArrayList集合修改次数modCount加锁或使用CopyOnWriteArrayList替换ArrayList,建议使用CopyOnWriteArrayList;

另外除了List集合外,其它集合像ConcurrentHashMap、CopyOnWriteArraySet也可以避免抛出ConcurrentModificationException异常;

什么是CopyOnWriteArrayList

看名字就知道,在往集合写操作的时候,复制集合;更具体地说,是在对集合结构进行修改的操作时,复制一个新的集合,然后在新的集合里进行结构修改(插入、删除),修改完成之后,改变原先集合内部数组的引用为新集合即可;

CopyOnWriteArrayList补充说明

CopyOnWriteArrayList类实现List<E>, RandomAccess, Cloneable, java.io.Serializable接口

与ArrayList功能类似,同样是基于动态数组实现的集合;

使用CopyOnWriteArrayList迭代器遍历的时候,读取的数据并不是实时的;

每次对集合结构进行修改时,都需要拷贝数据,占用内存较大;

源码查看

先看个简单的例子,add方法,如下,调用了Arrays.copyOf方法,拷贝旧数组到新数组,然后修改新数组的值,并修改集合内部数组的引用:

    public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}

再查看CopyOnWriteArrayList迭代器类的实现,部分代码如下, 在创建COWIterator迭代器的时候,仔细查看其构造器源码,需要将集合内部数组的引用传给迭代器对象,由于在集合修改的时候,操作都是针对新的拷贝数组,所以迭代器内部旧数组对象不会改变,保证迭代期间数据不会混乱(虽然不是实时的数据):

    private static class COWIterator<E> implements ListIterator<E> {
/** Snapshot of the array */
private final Object[] snapshot;
/** Index of element to be returned by subsequent call to next. */
private int cursor; private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
........

结果验证

利用前面抛出ConcurrentModificationException的例子,验证使用CopyOnWriteArrayList,

首先,创建一个t1线程,循环往集合插入数据,另外主线程获取集合迭代器遍历集合,代码如下,成功运行,并打印出了旧集合的数据(注意数据并不是实时的)。

package com.pichen.basis.col;

import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; class ThreadTest implements Runnable{ private List<Integer> list;
public ThreadTest(List<Integer> list) {
this.list = list;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.list.add(10);
}
}
} public class Main { public static void main(String[] args) throws InterruptedException { List<Integer> list = new CopyOnWriteArrayList<Integer>();
for(int i = 0; i < 10; i++){
list.add(i);
} ThreadTest t1 = new ThreadTest(list);
new Thread(t1).start(); Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
System.out.print(iterator.next() + " ");
Thread.sleep(80);
}
}
}

集合迭代器快速失败行为及CopyOnWriteArrayList的更多相关文章

  1. java ArrayList 迭代器快速失败源码分析

    先来看一个例子: @Test void test2() { ArrayList<String> list = new ArrayList<String>(); list.add ...

  2. Java SE之快速失败(Fast-Fail)与快速安全(Fast-Safe)的区别[集合与多线程/增强For](彻底详解)

    声明 特点:基于JDK源码进行分析. 研究费时费力,如需转载或摘要,请显著处注明出处,以尊重劳动研究成果:博客园 - https://www.cnblogs.com/johnnyzen/p/10547 ...

  3. Java 集合快速失败异常

    快速失败 在JDK中,查看集合有很多关于快速失败的描述: 注意,此实现不是同步的.如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步.(结构上的修改是指添 ...

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

    一.问题描述 话不多说,先上代码: public static void main(String[] args) throws InterruptedException { List<Strin ...

  5. Java集合框架中的快速失败(fail—fast)机制

      fail-fast机制,即快速失败机制,是java集合框架中的一种错误检测机制.多线程下用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加.删除),则会抛出Concurre ...

  6. 170118、快速失败Vs安全失败(Java迭代器附示例)

    简介: 当错误发生时,如果系统立即关闭,即是快速失败,系统不会继续运行.运行中发生错误,它会立即停止操作,错误也会立即暴露.而安全失败系统在错误发生时不会停止运行.它们隐蔽错误,继续运行,而不会暴露错 ...

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

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

  8. 快速失败(fail-fast)和安全失败(fail-safe)的区别

    1.fail-fast和fail-safe比较 Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响.java.util包下面的所有的集合类都是快速失败的,而java.ut ...

  9. 快速失败/报错机制 - fail-fast

    一.快速报错机制(fail-fast) 这是<Java编程思想>中关于快速报错机制的描述 Java容器有一种保护机制,能够防止多个进程同时修改同一个容器的内容.如果在你迭代遍历容器的过程中 ...

随机推荐

  1. 常用Eclipse快捷方式

    Ctrl+1 快速修复 Ctrl+D: 删除当前行 Ctrl+Alt+↓ 复制当前行到下一行(复制增加) Ctrl+Alt+↑ 复制当前行到上一行(复制增加) Alt+↓ 当前行和下面一行交互位置(特 ...

  2. C# ~ 数据库连接

    C#连接数据库(SQL Server)进行数据的读取和写入,学习之. 1. 数据库连接 服务器:“Data Source=服务器IP; Initial Catalog=数据库; User ID=用户名 ...

  3. C#-INotifyPropertyChanged(解决数据绑定的界面刷新问题)

    最近做项目用到DataGridView,用它绑定数据源后,如果数据源中的数据修改无法及时刷新到控件上,必须切换单元格的焦点才能导致刷新显示新数值,通过查官方文档,用INotifyPropertyCha ...

  4. thinkphp端口配置

    <?php return array( //'配置项'=>'配置值' 'MODULE_ALLOW_LIST' => array('Home'), 'DEFAULT_MODULE' = ...

  5. 通过python将图片生成字符画

    基础知识: 1.python基础知识   快速学习链接:https://www.shiyanlou.com/courses/214 2.linux命令行操作   快速学习链接:https://www. ...

  6. Hibernate(五)__hql语句

    hql(hibernate query language)功能强大. 首先回忆下之前我们接触的对数据对象的操作: ①删除session.delete(对象) ②保存session.save(对象)   ...

  7. [Tool] 取得APP的Store URL Scheme (Android、iOS)

    [Tool] 取得APP的Store URL Scheme (Android.iOS) 前言 在企业网站中,如果希望使用URL连结的方式,开启Store APP来下载APP(非网页下载).开发人员可以 ...

  8. netmiko初窥

    paramiko 和 pexpect在笔记里被跳过 因为,他们都可以被netmiko所取代,不想在他们身上浪费太多时间 补一个地方就是,如果用paramiko遇到了connection莫名自己关闭的情 ...

  9. CSS3盒模型温故

    CSS有一种基础设计模式叫盒模型,定义了Web页面中的元素是如何看做盒子来解析的.每一个盒子有不同的展示界面,下面就来介绍盒模型,主要有一下几种盒模型:inline.inline-block.bloc ...

  10. 【小贴士】虚拟键盘与fixed带给移动端的痛!

    前言 今天来公司的主要目的就是研究虚拟键盘与fixed的问题,期间因为同事问起闭包与事件委托(阻止冒泡)相关问题,便穿插了一篇别的: [小贴士]工作中的”闭包“与事件委托的”阻止冒泡“,有兴趣的朋友可 ...