【关于迭代器的for-each遍历集合现象。。。。。】
foreahc迭代集合元素的同时修改集合元素抛异常。。ConcurrentModificationException异常
只要使用迭代器遍历,其他集合遍历时进行增删操作都需要留意是否会触发ConcurrentModificationException异常。

一、单线程
1. 异常情况举例
只要抛出出现异常,可以肯定的是代码一定有错误的地方。先来看看都有哪些情况会出现ConcurrentModificationException异常,下面以ArrayList remove 操作进行举例:
使用的数据集合:
|
1
2
3
4
5
6
7
|
List<string> myList = new ArrayList<string>();myList.add( "1");myList.add( "2");myList.add( "3");myList.add( "4");myList.add( "5");</string></string> |
以下三种情况都会出现异常:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
Iterator<string> it = myList.iterator(); while (it.hasNext()) { String value = it.next(); if (value.equals( "3")) { myList.remove(value); // error }} for (Iterator<string> it = myList.iterator(); it.hasNext();) { String value = it.next(); if (value.equals( "3")) { myList.remove(value); // error }} for (String value : myList) { System. out.println( "List Value:" + value); if (value.equals( "3")) { myList.remove(value); // error }} </string></string> |
异常信息如下:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
at java.util.AbstractList$Itr.next(Unknown Source)
2. 根本原因
以上都有3种出现异常的情况有一个共同的特点,都是使用Iterator进行遍历,且都是通过ArrayList.remove(Object) 进行删除操作。
想要找出根本原因,直接查看ArrayList源码看为什么出现异常:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
public class ArrayList<e> extends AbstractList<e> implements Cloneable, Serializable, RandomAccess { @Override public boolean remove(Object object) { Object[] a = array; int s = size; if (object != null) { for (int i = 0; i < s; i++) { if (object.equals(a[i])) { System.arraycopy(a, i + 1, a, i, --s - i); a[s] = null; // Prevent memory leak size = s; modCount++; // 只要删除成功都是累加 return true; } } } else { for (int i = 0; i < s; i++) { if (a[i] == null) { System.arraycopy(a, i + 1, a, i, --s - i); a[s] = null; // Prevent memory leak size = s; modCount++; // 只要删除成功都是累加 return true; } } } return false; } @Override public Iterator<e> iterator() { return new ArrayListIterator(); } private class ArrayListIterator implements Iterator<e> { ...... // 全局修改总数保存到当前类中 /** The expected modCount value */ private int expectedModCount = modCount; @SuppressWarnings("unchecked") public E next() { ArrayList<e> ourList = ArrayList.this; int rem = remaining; // 如果创建时的值不相同,抛出异常 if (ourList.modCount != expectedModCount) { throw new ConcurrentModificationException(); } if (rem == 0) { throw new NoSuchElementException(); } remaining = rem - 1; return (E) ourList.array[removalIndex = ourList.size - rem]; } ...... }} </e></e></e></e></e> |
List、Set、Map 都可以通过Iterator进行遍历,这里仅仅是通过List举例,在使用其他集合遍历时进行增删操作都需要留意是否会触发ConcurrentModificationException异常。
3. 解决方案
上面列举了会出现问题的几种情况,也分析了问题出现的根本原因,现在来总结一下怎样才是正确的,如果避免遍历时进行增删操作不会出现ConcurrentModificationException异常。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
// 1 使用Iterator提供的remove方法,用于删除当前元素 for (Iterator<string> it = myList.iterator(); it.hasNext();) { String value = it.next(); if (value.equals( "3")) { it.remove(); // ok }}System. out.println( "List Value:" + myList.toString()); // 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的位置 }}System. out.println( "List Value:" + myList.toString());</string></string></string></string></string></string> |
输出结果都是:List Value:[1, 2, 4, 5] , 不会出现异常。
以上4种解决办法在单线程中测试完全没有问题,但是如果在多线程中呢?
二、多线程
1. 同步异常情况举例
上面针对ConcurrentModificationException异常在单线程情况下提出了4种解决方案,本来是可以很哈皮的洗洗睡了,但是如果涉及到多线程环境可能就不那么乐观了。
下面的例子中开启两个子线程,一个进行遍历,另外一个有条件删除元素:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
final List<string> myList = createTestData();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 (Iterator<string> it = myList.iterator(); it.hasNext();) { String value = it.next(); System.out.println("删除元素 value = " + value); if (value.equals( "3")) { it.remove(); } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }}).start();</string></string> |
输出结果:
遍历集合 value = 1
删除元素 value = 1
遍历集合 value = 2
删除元素 value = 2
遍历集合 value = 3
删除元素 value = 3
Exception in thread "Thread-0" 删除元素 value = 4
java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
at java.util.AbstractList$Itr.next(Unknown Source)
at list.ConcurrentModificationExceptionStudy$1.run(ConcurrentModificationExceptionStudy.java:42)
at java.lang.Thread.run(Unknown Source)
删除元素 value = 5
结论:
上面的例子在多线程情况下,仅使用单线程遍历中进行删除的第1种解决方案使用it.remove(),但是测试得知4种的解决办法中的1、2、3依然会出现问题。
接着来再看一下JavaDoc对java.util.ConcurrentModificationException异常的描述:
当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常。
说明以上办法在同一个线程执行的时候是没问题的,但是在异步情况下依然可能出现异常。
2. 尝试方案
(1) 在所有遍历增删地方都加上synchronized或者使用Collections.synchronizedList,虽然能解决问题但是并不推荐,因为增删造成的同步锁可能会阻塞遍历操作。
(2) 推荐使用ConcurrentHashMap或者CopyOnWriteArrayList。
3. CopyOnWriteArrayList注意事项
(1) CopyOnWriteArrayList不能使用Iterator.remove()进行删除。
(2) CopyOnWriteArrayList使用Iterator且使用List.remove(Object);会出现如下异常:
java.lang.UnsupportedOperationException: Unsupported operation remove
at java.util.concurrent.CopyOnWriteArrayList$ListIteratorImpl.remove(CopyOnWriteArrayList.java:804)
4. 解决方案
单线程情况下列出4种解决方案,但是发现在多线程情况下仅有第4种方案才能在多线程情况下不出现问题。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
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();</string></string> |
输出结果:
删除元素 value = 1
遍历集合 value = 1
删除元素 value = 2
遍历集合 value = 2
删除元素 value = 3
遍历集合 value = 3
删除元素 value = 4
遍历集合 value = 4
删除元素 value = 5
遍历集合 value = 5
【关于迭代器的for-each遍历集合现象。。。。。】的更多相关文章
- java基础29 迭代器 listIterator() 及各种遍历集合的方法
listIterator() 迭代器包含了 Iterator() 迭代器中的所有方法. 1.ListIterator的常用方法 hasPrevious() :判断是否还有上一个元素,有则返回true ...
- 迭代器:遍历集合元素的操作. iterator()
package seday11; import java.util.ArrayList;import java.util.Collection;import java.util.Iterator; / ...
- JAVA基础学习之final关键字、遍历集合、日期类对象的使用、Math类对象的使用、Runtime类对象的使用、时间对象Date(两个日期相减)(5)
1.final关键字和.net中的const关键字一样,是常量的修饰符,但是final还可以修饰类.方法.写法规范:常量所有字母都大写,多个单词中间用 "_"连接. 2.遍历集合A ...
- java中遍历集合的三种方式
第一种遍历集合的方式:将集合变为数组 package com.lw.List; import java.util.ArrayList; import java.util.List; import ja ...
- java8新特性,使用流遍历集合
在这篇“Java 8新特性教程”系列文章中,我们会深入解释,并通过代码来展示,如何通过流来遍历集合,如何从集合和数组来创建流,以及怎么聚合流的值. 在之前的文章“遍历.过滤.处理集合及使用Lambda ...
- 遍历集合的Iterator删除其中的元素
package list; import java.util.LinkedList; /* * 遍历集合的时候删除其中的元素 从后往前删,每次都删除的是最后一个元素,不涉及移位 */public cl ...
- 增强for循环遍历集合或数组
遍历:for循环遍历数组或集合:iterator迭代器遍历集合:还有增强for循环(for each)遍历数组或集合: 遍历数组: 遍历集合:
- Java遍历集合的几种方法分析(实现原理、算法性能、适用场合)
概述 Java语言中,提供了一套数据集合框架,其中定义了一些诸如List.Set等抽象数据类型,每个抽象数据类型的各个具体实现,底层又采用了不同的实现方式,比如ArrayList和LinkedList ...
- java8 增强的Iterator遍历集合元素
Iterator接口也是Java集合框架的成员,与Collection和Map两个系列的集合不一样的是Collection和Map系列主要用于充当容器的作用,而Iterator正如其名字一样是主要用于 ...
随机推荐
- ASP.NET Core URL Rewrite中间件
URL重写是基于一个或多个预置规则修改请求URL的行为.URL重写在资源位置和访问地址之间创建了一种抽象,这样二者之间就减少了紧密的联系.URL重写有多种适用的场景: 临时或永久移动或替换服务器资源, ...
- 如何安全地运行用户的 JavaScript 脚本
本文来自网易云社区,转载务必请注明出处. 有时候我们需要运行用户输入的 JavaScript 脚本(以下简称脚本).对于我们来说,这些脚本是不可信任的,如果在当前的 Context 中运行这些脚本,它 ...
- 为啥我的Andrid百度SDK不能正常运行?
首先,百度SDK运行时候需要加载API Key,这个API Key是通过百度账号在百度里面申请到的,但是很可能大家没有注意到百度这个API Key申请的过程,其中很重要的一个步骤没有做好,而导致不能正 ...
- Mysql数据操作《三》多表查询
多表连接查询 复合条件连接查询 子查询 准备表 #建表 create table department( id int, name varchar(20) ); create table employ ...
- day04.2-迭代器
1. 迭代器协议: 对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代(只能往后走,不能往前退). 2. 可迭代对象:实现了迭代器 ...
- for循环 | range 对象
# ### for循环 # 循环 遍历 迭代 # 把列表的元素一一的拿出来遍历 listvar = ["黄雄大","黄文","黄仪正",&q ...
- P4597 序列sequence
传送门 题解 完全看不懂大佬们在说什么……特别是chen_zhe大佬写的…… 来说说个人的理解吧 大佬们说:考虑当前的数$x$和之前的最大数$y$,(默认$x<y$,因为如果$x>=y$已 ...
- iptables总结
iptables: 包过滤型防火墙 Firewall: 防火墙,隔离工具:工作于主机或网络的边缘,对于进出本主机或网络的报文根据事先定义好的检查规则作匹配检测,对于能够被规则所匹配到 ...
- python 学习(pip工具的安装)
mac 电脑上使用终端命令 curl https://bootstrap.pypa.io/get-pip.py | python3 基于Python 3 pip --version pip3 list ...
- FPGA基础学习(7) -- 内部结构之CLB
目录 1. 总览 2. 可配置逻辑单元 2.1 6输入查找表(LUT6) 2.2 选择器(MUX) 2.3 进位链(Carry Chain) 2.4 触发器(Flip-Flop) 参考文献: 一直以来 ...