博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
集合迭代器快速失败行为及CopyOnWriteArrayList
阅读量:7045 次
发布时间:2019-06-28

本文共 5771 字,大约阅读时间需要 19 分钟。

以下内容基于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
list = new ArrayList
(); for(int i = 0; i < 10; i++){ list.add(i); } Iterator
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
list; public ThreadTest(List
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
list = new ArrayList
(); for(int i = 0; i < 10; i++){ list.add(i); } ThreadTest t1 = new ThreadTest(list); new Thread(t1).start(); Iterator
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
implements ListIterator
{ /** 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
list; public ThreadTest(List
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
list = new CopyOnWriteArrayList
(); for(int i = 0; i < 10; i++){ list.add(i); } ThreadTest t1 = new ThreadTest(list); new Thread(t1).start(); Iterator
iterator = list.iterator(); while(iterator.hasNext()){ System.out.print(iterator.next() + " "); Thread.sleep(80); } }}

 

转载于:https://www.cnblogs.com/chenpi/p/5270990.html

你可能感兴趣的文章
团队绩效考核的思考
查看>>
死磕 Elasticsearch 方法论:普通程序员高效精进的 10 大狠招!(Elasticsearch教程序章)|MVP讲堂...
查看>>
PostgreSQL 10.1 手册_部分 II. SQL 语言_第 10 章 类型转换_10.6. SELECT 输出列
查看>>
使用Java类加载SpringBoot、SpringCloud配置文件
查看>>
Java枚举
查看>>
如何把 Markdown 文件批量转换为 pdf?
查看>>
给信息安全爱好者的一封信
查看>>
swift 协议的写时拷贝
查看>>
为什么会出现微服务和分布式?
查看>>
SpringMVC源码分析3:DispatcherServlet的初始化与请求转发
查看>>
《当幸福来敲门》观后感
查看>>
Confluence 6 后台中的默认空间模板设置
查看>>
人工生命 1.0.0 版发布,第一个人工生命诞生
查看>>
SpringBlade 2.0.1 发布,兼容jdk11与openjdk,增加Saber代码生成功能
查看>>
商城系统 DBShop V1.3 Release 20190309 发布
查看>>
awk 金典去除原理解析
查看>>
burpsuit+天天模拟器(手机app数据包抓包改包)
查看>>
Javascript 创建对象方法的总结
查看>>
docker : 报错 WARNING: IPv4 forwarding is disabled. Networking will not work.
查看>>
Hyper-V故障转移群集搭建(3)
查看>>