List remove ConcurrentModificationException源码分析
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.mashibing.tank.Test2.main(Test2.java:14)
ArrayList在迭代删除元素时,如果使用不当会发生concurrentModification 异常,常见的错误使用场景
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer next = iterator.next();
list.remove(next);
}
for (Integer next:list
) {
list.remove(next);
}
发生异常原因:
首先明确以上两种方式是等效的,原因:增强for在反编译后语句
Iterator var4 = var1.iterator();
while(var4.hasNext()) {
Integer var3 = (Integer)var4.next();
var1.remove(var3);
}
然后我们去分析发生concurrentModification原因:
由于异常发生在 :java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909),我们先去看下此处代码
发现remove(intxOrObject) 方法为 ArrayList 内部类Itr的一个方法 ,当modCount != expectedModCount 时会抛出异常ConcurrentModificationException。
先来解释下概念:
modCount:为 成员变量 list 被修改的次数 ,每次add ,remove都会+1 (The number of times this list has been )
public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E>{
//The number of times this list has been
protected transient int modCount = 0;
}
expectedModCount:为listIterator 方法 局部变量,表示对ArrayList修改次数的期望值,它的初始值为modCount
public Iterator<E> iterator() {
return listIterator();
}
public ListIterator<E> listIterator(final int index) {
checkForComodification();
rangeCheckForAdd(index);
final int offset = this.offset;
return new ListIterator<E>() {
int cursor = index;
int lastRet = -1;
//注意这里
int expectedModCount = ArrayList.this.modCount;
.......
}
....
}
.....
}
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
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];
}
}
这里假设原始list 长度为10(只做过新增,没有做过删除操作) 。
这时候最初 AbstractList.modCount =10 ;
Iterator.expectedModCount = modCount =10 ;
1.当第一次循环调用next时,无变动list ,所以expectedModCount = modCount =10 ,
2.当 arrayList.remove(indexOrObject)时,modCount++ 变为11 ,expectedModCount 依然为10 。
3.第二次调用next 时,Itr 会执行checkForComodification() ,也就是判断expectedModCount、 modCount 是否一致,如果不一致则抛出ConcurrentModificationException 。
正确姿势:
关注点:
1.增强for循环不要删除元素
2.使用Iterator<Integer> iterator = list.iterator(); 方式时,先获取元素,然后使用iterator.remove() 进行删除
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext() ) {
System.out.println(iterator.next());
iterator.remove();
}
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
list.remove(i);
}
为什么Iterator<Integer> iterator = list.iterator(); 方式时 用iterator remove 不会出现问题?看源码:
private class Itr implements Iterator<E> {
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
//关注这里, Itr里将modCount 赋值给expectedModCount
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
为什么普通循环不会有ConcurrentModificationException ,看源码:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
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
}
}
List remove ConcurrentModificationException源码分析的更多相关文章
- 集合操作出现的ConcurrentModificationException(源码分析)
摘要: 为了保证线程安全,在迭代器迭代的过程中,线程是不能对集合本身进行操作(修改,删除,增加)的,否则会抛出ConcurrentModificationException的异常. 示例: publi ...
- Java并发-ConcurrentModificationException原因源码分析与解决办法
一.异常原因与异常源码分析 对集合(List.Set.Map)迭代时对其进行修改就会出现java.util.ConcurrentModificationException异常.这里以ArrayList ...
- HashMap与TreeMap源码分析
1. 引言 在红黑树--算法导论(15)中学习了红黑树的原理.本来打算自己来试着实现一下,然而在看了JDK(1.8.0)TreeMap的源码后恍然发现原来它就是利用红黑树实现的(很惭愧学了Ja ...
- 【集合框架】JDK1.8源码分析之HashMap & LinkedHashMap迭代器(三)
一.前言 在遍历HashMap与LinkedHashMap时,我们通常都会使用到迭代器,而HashMap的迭代器与LinkedHashMap迭代器是如何工作的呢?下面我们来一起分析分析. 二.迭代器继 ...
- 【JUC】JDK1.8源码分析之CopyOnWriteArrayList(六)
一.前言 由于Deque与Queue有很大的相似性,Deque为双端队列,队列头部和尾部都可以进行入队列和出队列的操作,所以不再介绍Deque,感兴趣的读者可以自行阅读源码,相信偶了Queue源码的分 ...
- 【JUC】JDK1.8源码分析之ConcurrentSkipListSet(八)
一.前言 分析完了CopyOnWriteArraySet后,继续分析Set集合在JUC框架下的另一个集合,ConcurrentSkipListSet,ConcurrentSkipListSet一个基于 ...
- HashMap实现原理及源码分析
哈希表(hash table)也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常出 ...
- JDK Collection 源码分析(2)—— List
JDK List源码分析 List接口定义了有序集合(序列).在Collection的基础上,增加了可以通过下标索引访问,以及线性查找等功能. 整体类结构 1.AbstractList 该类作为L ...
- [源码解析]HashMap和HashTable的区别(源码分析解读)
前言: 又是一个大好的周末, 可惜今天起来有点晚, 扒开HashMap和HashTable, 看看他们到底有什么区别吧. 先来一段比较拗口的定义: Hashtable 的实例有两个参数影响其性能:初始 ...
随机推荐
- 【转】mac os x配置adb命令的方法,苹果电脑设置adb命令的方法
http://www.myexception.cn/operating-system/1636963.html 步骤如下: 1. 启动终端Terminal (如果当前用户文件夹下已有.bash_pro ...
- codeigniter注意点
1. 数据的操作: insert 和update最好使用自带的db方法,1避免了过滤字符串和sql注入,2是数据量相对查询是非常小的. select则建议使用自己写的,这样便于优化. 2. 去掉ind ...
- mysql 优化配置和方面
MySQL性能优化的参数简介 公司网站访问量越来越大,MySQL自然成为瓶颈,因此最近我一直在研究 MySQL 的优化,第一步自然想到的是 MySQL 系统参数的优化,作为一个访问量很大的网站(日20 ...
- Serializable 接口(序列化)
目录 Serializable 接口(序列化) 前言 用途 如何实现 异常 serialVersionUID transient关键字 Serializable 接口(序列化) 前言 查看API文档时 ...
- redis下载安装及php配置redis
下载及安装redis 1.首先去github网站上下载https://github.com/dmajkic/redis/downloads: 2.根据实际情况,将64bit的内容cp到自定义盘符目录, ...
- 关于Git的版本控制
1.关于版本控制? 版本控制是一种记录文件或文件集随时间变化的系统,以便您以后可以调用特定版本,如果您是图形或Web设计人员并希望保留图像或布局的每个版本(您肯定希望这样),则使用版本控制系统(VCS ...
- SWUST OJ 爬不出去的水井(0333)
爬不出去的水井(0333) Time limit(ms): 1000 Memory limit(kb): 65535 Submission: 1069 Accepted: 150 Descriptio ...
- 通过ELK快速搭建集中化日志平台
ELK就是ElasticSearch + LogStash + Kibana 1.准备工作 ELK下载:https://www.elastic.co/downloads/ jdk version:1. ...
- sql问题处理
批量杀死MySQL连接 select concat('KILL ',id,';') from information_schema.processlist where Info like 'selec ...
- Java8-19-lambda 重构代码
通过本书的前七章,我们了解了Lambda和Stream API的强大威力. 你可能主要在新项目的代码中使用这些特性.如果你创建的是全新的Java项目,这是极好的时机,你可以轻装上阵,迅速地将新特性应用 ...