Java并发-ConcurrentModificationException原因源码分析与解决办法
一、异常原因与异常源码分析
对集合(List、Set、Map)迭代时对其进行修改就会出现java.util.ConcurrentModificationException异常。这里以ArrayList为例,例如下面的代码:
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
//遍历1
for (String s : list){
if (s.equals( "3")) {
list.remove(s); // error
}
}
//遍历2
Iterator<String> it = list.iterator();
for (; it.hasNext();) {
String value = it.next();
if (value.equals("3")) {
list.remove(value); // error
}
}
ArrayList类中包含了实现Iterator迭代器的内部类Itr,在Itr类内部维护了一个expectedModCount变量,而在ArrayList类中维护一个modCount变量(modCount是ArrayList实现AbstractList类得到成员变量)。其他集合(List、Set、Map)都与之类似。

当对集合进行添加或者删除操作时modCount的值都会进行modCount++操作,例如ArrayList中的remove()方法:
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
}
当集合添加完值后,对集合进行遍历时才会创建Itr对象,这时候会执行int expectedModCount = modCount;操作,也就是说只要是在增加或删除后对集合进行遍历,那expectedModCount 与modCount永远是相等的。
但是如果在遍历的过程中进行增加或删除操作那么modCount++,但是expectedModCount保存的还是遍历前的值,也就是expectedModCount和modCount的值是不相等的。
遍历过程中会调用iterator的next()方法,next()方法方法会首先调用checkForComodification()方法来验证expectedModCount和modCount是否相等,因为之前做了增加或删除操作,modCount的值发生了变化,所以expectedModCount和modCount不相等,抛出ConcurrentModificationException异常。
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
二、单线程解决方案
1、迭代器删除
在Itr类中也给出了一个remove()方法,通过调用Itr类的方法就可以实现而且不报错,例如下面代码:
ArrayList<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.remove("4");
//遍历2
Iterator<String> it = list.iterator();
for (; it.hasNext();) {
String value = it.next();
if (value.equals("3")) {
it.remove();
}
}
在Itr类中remove()方法中,执行了expectedModCount = modCount操作,那么执行next()方法时expectedModCount和modCount肯定相等,Itr类中remove()方法的源码:
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
2、其他的方式
// 2 建一个集合,记录需要删除的元素,之后统一删除
List<string> templist = new ArrayList<string>();
for (String value : myList) {
if (value.equals( "3")) {
templist.remove(value);
}
}
// 可以查看removeAll源码,其中使用Iterator进行遍历
myList.removeAll(templist);
System. out.println( "List Value:" + myList.toString()); // 3. 使用线程安全CopyOnWriteArrayList进行删除操作
List<string> myList = new CopyOnWriteArrayList<string>();
myList.add( "1");
myList.add( "2");
myList.add( "3");
myList.add( "4");
myList.add( "5"); Iterator<string> it = myList.iterator(); while (it.hasNext()) {
String value = it.next();
if (value.equals( "3")) {
myList.remove( "4");
myList.add( "6");
myList.add( "7");
}
}
System. out.println( "List Value:" + myList.toString()); // 4. 不使用Iterator进行遍历,需要注意的是自己保证索引正常
for ( int i = 0; i < myList.size(); i++) {
String value = myList.get(i);
System. out.println( "List Value:" + value);
if (value.equals( "3")) {
myList.remove(value); // ok
i--; // 因为位置发生改变,所以必须修改i的位置
}
}
三、多线程解决方案
1、多线程下异常原因
多线程下ArrayLis用Itr类中remove()方法也是会报异常的,Vector(线程安全)也会出现这种错误,具体原因如下:
Itr是在遍历的时候创建的,也就是每个线程如果遍历都会得到一个expectedModCount ,expectedModCount 也就是每个线程私有的,假若此时有2个线程,线程1在进行遍历,线程2在进行修改,那么很有可能导致线程2修改后导致Vector中的modCount自增了,线程2的expectedModCount也自增了,但是线程1的expectedModCount没有自增,此时线程1遍历时就会出现expectedModCount不等于modCount的情况了。
2、尝试方案
(1) 在所有遍历增删地方都加上synchronized或者使用Collections.synchronizedList,虽然能解决问题但是并不推荐,因为增删造成的同步锁可能会阻塞遍历操作。
(2) 推荐使用ConcurrentHashMap或者CopyOnWriteArrayList。
3、CopyOnWriteArrayList使用注意
(1) CopyOnWriteArrayList不能使用Iterator.remove()进行删除。
(2) CopyOnWriteArrayList使用Iterator且使用List.remove(Object);会出现如下异常:
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4"); Iterator<String> it = list.iterator();
for (; it.hasNext();) {
String value = it.next();
if (value.equals("4")) {
it.remove(); // error
}
} Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.concurrent.CopyOnWriteArrayList$COWIterator.remove(CopyOnWriteArrayList.java:1040)
at TestZzl.main(TestZzl.java:51)
4、最终解决方案
List<string> myList = new CopyOnWriteArrayList<string>();
myList.add( "1");
myList.add( "2");
myList.add( "3");
myList.add( "4");
myList.add( "5"); new Thread(new Runnable() { @Override
public void run() {
for (String string : myList) {
System.out.println("遍历集合 value = " + string); try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start(); new Thread(new Runnable() { @Override
public void run() {
for (int i = 0; i < myList.size(); i++) {
String value = myList.get(i); System.out.println("删除元素 value = " + value); if (value.equals( "3")) {
myList.remove(value);
i--; // 注意
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
后续会具体分析一下CopyOnWriteArrayList
参考:
https://www.2cto.com/kf/201403/286536.html
https://www.cnblogs.com/dolphin0520/p/3933551.html
Java并发-ConcurrentModificationException原因源码分析与解决办法的更多相关文章
- Java并发编程-ReentrantLock源码分析
一.前言 在分析了 AbstractQueuedSynchronier 源码后,接着分析ReentrantLock源码,其实在 AbstractQueuedSynchronizer 的分析中,已经提到 ...
- Java并发编程 ReentrantLock 源码分析
ReentrantLock 一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大. 这个类主要基于AQS(Abst ...
- Java并发编程-AbstractQueuedSynchronizer源码分析
简介 提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架.该同步器(以下简称同步器)利用了一个int来表示状态,期望它能够成为实现大部分同步需求的基础.使用的方法是继承,子类通过 ...
- Java并发编程 LockSupport源码分析
这个类比较简单,是一个静态类,不需要实例化直接使用,底层是通过java未开源的Unsafe直接调用底层操作系统来完成对线程的阻塞. package java.util.concurrent.locks ...
- Java并发指南10:Java 读写锁 ReentrantReadWriteLock 源码分析
Java 读写锁 ReentrantReadWriteLock 源码分析 转自:https://www.javadoop.com/post/reentrant-read-write-lock#toc5 ...
- 【死磕 Java 集合】— ConcurrentSkipListMap源码分析
转自:http://cmsblogs.com/?p=4773 [隐藏目录] 前情提要 简介 存储结构 源码分析 主要内部类 构造方法 添加元素 添加元素举例 删除元素 删除元素举例 查找元素 查找元素 ...
- 死磕 java集合之DelayQueue源码分析
问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...
- 死磕 java集合之PriorityBlockingQueue源码分析
问题 (1)PriorityBlockingQueue的实现方式? (2)PriorityBlockingQueue是否需要扩容? (3)PriorityBlockingQueue是怎么控制并发安全的 ...
- 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计
问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...
随机推荐
- [PHP]Nginx与PHP的文件上传大小限制
---------------------------------------------------------------------------------------------------- ...
- 在Virtualenv中使用Idle
原创 在Virtualenv中使用Idle 本文适用于Linux系统,在DebianTesting上测试通过. 关于Virtualenv 先看一段Virtualenv这货的官方介绍: virtuale ...
- Nginx深度优化
简介 1.隐藏版本号2.修改Nginx用户与组3.配置Nginx网页缓存时间4.实现Nginx的日志切割5.配置Nginx实现连接超时6.更改进程数7.配置Nginx实现网页压缩功能8.配置Nginx ...
- unity3d API知识点随记
1.transform.translate是增加transform面板相应的数值x,y,z是以本地坐标系为方向:transform.transformdirection是以世界坐标系为方向,可以去测试 ...
- python opencv3 给图片加中文
转自:https://www.cnblogs.com/arkenstone/p/6961453.html opencv3.2将中文输出到图片上 opencv自带的putText函数无法输出utf8类型 ...
- UMD模式的js
(function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD define( ...
- centos7 增加虚拟网卡
确认内核是否有tun模块 # modinfo tun yum install tunctl -y 如果找不到 vim /etc/yum.repos.d/nux-misc.repo [nux-misc] ...
- php页面开启错误提示
ini_set("display_errors", "On"); error_reporting(E_ALL | E_STRICT);
- matlab stereo_gui立体标定
http://www.vision.caltech.edu/bouguetj/calib_doc/index.html#examples 文档中举了几个例子,有关双目的是第5个, 这个例子展示了如何使 ...
- Spring 手动 提交事务
1.配置文件 applicationContext.xml: <bean id="txManager"class="org.springframework.jdbc ...