java list 容器的ConcurrentModificationException
java中的很多容器在遍历的同时进行修改里面的元素都会ConcurrentModificationException,包括多线程情况和单线程的情况。多线程的情况就用说了,单线程出现这个异常一般是遍历(forEach)过程中的修改导致了list中的状态不一致,为了防止不一致带来不可预测的后果所以抛出异常。以ArrayList为例,每次操作都会进行内部状态检查,代码如下所示:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
当这两个值不一样,就意味着之前的操作出现了异常。其中modCount是定义在AbstractList中,用来表示列表被修改的次数。expectedModCount则是定义在Iterator父类中,默认值等于modCount用来表示期望的修改次数。
但是有时候确实有遍历修改的需要,如遍历过程中删除不需要的条目。这种需求在单线程下也是可以满足的,可以使用以下两种方式:
public void modifyIterator() {
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String str = iterator.next();
if(str.equals(""))
iterator.remove();
}
}
public void modifyByIndex() {
for(int i=0;i<list.size();i++){
String str = list.get(i)
if(str.equals(""))
list.remove(i);
}
}
通常使用Iterator,后者不常用,而且因为ArrayList在删除某个元素后元素会移动,因此会造成索引变化,对于遍历会有影响。但是如果使用forEach则会报错(也是新手容易犯的错误):
public void modifyForEach() {
for(String str:list){
list.remove(str);
}
}
如果你使用上面这样的代码必定会报错。为什么这种形式就会报错呢?首先看一下两个函数的机器码:
public void modifyIterator();
Code:
0: aload_0
1: getfield #17 // Field list:Ljava/util/List;
4: invokeinterface #34, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
9: astore_1
10: goto 19
13: aload_1
14: invokeinterface #56, 1 // InterfaceMethod java/util/Iterator.remove:()V
19: aload_1
20: invokeinterface #49, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
25: ifne 13
28: return
} public void modifyForEach();
Code:
0: aload_0
1: getfield #17 // Field list:Ljava/util/List;
4: invokeinterface #34, 1 // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
9: astore_2
10: goto 34
13: aload_2
14: invokeinterface #38, 1 // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
19: checkcast #44 // class java/lang/String
22: astore_1
23: aload_0
24: getfield #17 // Field list:Ljava/util/List;
27: aload_1
28: invokeinterface #46, 2 // InterfaceMethod java/util/List.remove:(Ljava/lang/Object;)Z
33: pop
34: aload_2
35: invokeinterface #49, 1 // InterfaceMethod java/util/Iterator.hasNext:()Z
40: ifne 13
43: return
可以看到forEach的遍历过程也使用了Iterator。但是为什么后者会报错呢?原因其实很简单,forEach在开始时候获得Iterator,删除过程中没有更新Iterator的状态,所以导致了最后的状态不一致。首先看一下Iterator的删除代码:
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; // clear to let GC do its work
}
fastRemove方法中修改了modCount,但是由于Iterator已经回去,它里面的expectedModCount却没有更新,因此导致了两者的不一致。
总结:多线程同时修改list或单线程使用forEach遍历修改list过程,很多list会报ConcurrentModificationException错误。这个错误是由于Iterator中的expectedModCount与list中modCount不一致导致的。因为forEach也是使用了Iterator,但是修改却使用了list的相关方法,Iterator不会随着更新。所以多线程情况下任何操作需要同步避免list中数据不一致,而单线程的遍历修改一定要使用Iterator。
java list 容器的ConcurrentModificationException的更多相关文章
- Java集合容器简介
Java集合容器主要有以下几类: 1,内置容器:数组 2,list容器:Vetor,Stack,ArrayList,LinkedList, CopyOnWriteArrayList(1.5),Attr ...
- java并发容器(Map、List、BlockingQueue)
转发: 大海巨浪 Java库本身就有多种线程安全的容器和同步工具,其中同步容器包括两部分:一个是Vector和Hashtable.另外还有JDK1.2中加入的同步包装类,这些类都是由Collectio ...
- java并发容器(Map、List、BlockingQueue)具体解释
Java库本身就有多种线程安全的容器和同步工具,当中同步容器包含两部分:一个是Vector和Hashtable.另外还有JDK1.2中增加的同步包装类.这些类都是由Collections.synchr ...
- [转载]Java集合容器简介
Java集合容器主要有以下几类: 1,内置容器:数组 2,list容器:Vetor,Stack,ArrayList,LinkedList, CopyOnWriteArrayList(1.5),Attr ...
- java 并发容器一之BoundedConcurrentHashMap(基于JDK1.8)
最近开始学习java并发容器,以补充自己在并发方面的知识,从源码上进行.如有不正确之处,还请各位大神批评指正. 前言: 本人个人理解,看一个类的源码要先从构造器入手,然后再看方法.下面看Bounded ...
- Java并发编程系列-(5) Java并发容器
5 并发容器 5.1 Hashtable.HashMap.TreeMap.HashSet.LinkedHashMap 在介绍并发容器之前,先分析下普通的容器,以及相应的实现,方便后续的对比. Hash ...
- 面霸篇:Java 集合容器大满贯(卷二)
面霸篇,从面试角度作为切入点提升大家的 Java 内功,所谓根基不牢,地动山摇. 码哥在 <Redis 系列>的开篇 Redis 为什么这么快中说过:学习一个技术,通常只接触了零散的技术点 ...
- JAVA的容器---List,Map,Set (转)
JAVA的容器---List,Map,Set Collection├List│├LinkedList│├ArrayList│└Vector│ └Stack└SetMap├Hashtable├HashM ...
- [转载]四大Java EE容器
转载自: https://my.oschina.net/diedai/blog/271367 现在流行的Java EE容器有很多:Tomcat.JBoss.Resin.Glassfish等等.下面对这 ...
随机推荐
- SQL 小技能
1. SET STATISTICS TIMEON 附带原文 2.关于索引,是自己可以用Tsql语句建,也可以在设计表的时候选中某一个字段建立索引 一般而言,主键属于聚合索引(字典:A-Z);反 ...
- Coderfroces 864 E. Fire(01背包+路径标记)
E. Fire http://codeforces.com/problemset/problem/864/E Polycarp is in really serious trouble — his h ...
- NET 高效开发之不可错过的实用工具(第一的当然是ReSharper插件)
工欲善其事,必先利其器,没有好的工具,怎么能高效的开发出高质量的代码呢?本文为 ASP.NET 开发者介绍一些高效实用的工具,包括 SQL 管理,VS插件,内存管理,诊断工具等,涉及开发过程的各个环节 ...
- Ubuntu16.04添加HP Laserjet Pro M128fn打印机和驱动
一.全部设置->打印机->添加新打印机 添加打印机 二.选择自动搜索到的网络打印机HP Laserjet Pro M128fn,点击添加. 三.添加打印机完成,打印测试页进行测试. 四. ...
- 实现人脸识别性别之路---open CV将图片显示出来
import cv2filename='E:\\tensorflow\\bu.jpg'#图片的地址 # face_cascade=cv2.CascadeClassifier('C:\\anconda3 ...
- 【RHEL7/CentOS7服务控制之systemctl命令】
Systemd对于Linux来说,就是一个init程序,可以作为sysVinit和Upstat的替代. RHEL7监控和控制Systemd的主要命令是systemctl,该命令可查看系统状态和管理系统 ...
- 【Henu ACM Round#14 D】Kefa and Dishes
[链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 状态压缩动规. 可以写成记忆化搜索的形式. f[bit][p] 表示选取的菜的情况为bit(用0..2^(N)-1的二进制形式表示各 ...
- Codeforces Beta Round #2 C. Commentator problem
模拟退火果然是一个非常高端的东西,思路神马的全然搞不懂啊~ 题目大意: 给出三个圆,求一点到这三个圆的两切线的夹角相等. 解题思路: 对于这个题来说还是有多种思路的 .只是都搞不明确~~ /害羞脸 ...
- 比MD5 和HMAC还要安全的加密 - MD5 加时间戳
//1.给一个字符串进行MD5加密 NSString *passKey = @"myapp"; passKey = [passKey md5String]; //2.对第一步中得到 ...
- pip的认识
一.pip与python的关系:pip并不是一种语言,而是一个Python包管理工具,主要是用于安装 PyPI 上的软件包.安装好pip之后,使用pip install 命令即可方便的安装python ...