ConcurrentModificationException异常的解决
一. 前言
最近一个学生,在使用使用ArrayList的subList的时候,发生了ConcurrentModificationException的异常。老师觉得这个现象非常具有代表性,估计有不少同学都会在同样的问题上犯迷糊,所以今天特意把这个问题记录下来,供大家参考。
二. 异常场景
1. 代码如下
下面是产生异常的代码。
public static void main(String[] args) throws InterruptedException {
// 1 定义一个String的集合,并且添加3个元素 【"z","k","x"】
List sourceList = new ArrayList(2);
sourceList.add("z");
sourceList.add("k");
sourceList.add("x");
// 2 使用subList方法 表面上把集合的前2个元素截取出来放在新的集合newList中
// 表面上此时newList 应该是【"z","k"】
List newList = sourceList.subList(0, 2);
// 3 往sourceList的0号位置 添加新的元素 sourceList应该变成了 【“cs”,"z","k",“x”】
sourceList.add(0,"cs");
// 4 意图在控制台上打印新的集合newList的0号位置 想观察一次此时newList的0号元素到底是什么
// 结果如果是 "z" 说明:【newList 是 sourceList的副本,此时两者已经没什么关系】
// 结果如果是 "cs" 说明:【newList 是 sourceList的视图,修改sourceList会影响到newList】
// 结果意想不到的是 这个地方抛出了ConcurrentModificationException 异常
System.out.println(newList.get(0));
}
2. 异常截图
三. 原因分析
3.1 modCount变量
ArrayList 中定义了一个变量modCount,顾名思义这个变量就是用来记录ArrayList被修改的次数,modCount 初始值为0,ArrayList 源代码如下。
protected transient int modCount = 0;
3.2 modCount解析
ArrayList 中每每有增删改的变动,都会导致modCount加1,源代码如下。
// 例如 ArrayList 的 add 方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!! 在这里modCount+1
elementData[size++] = e;
return true;
}
// 继续深入到 ensureCapacityInternal 方法
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity); // 在这里modCount+1 这里再继续深入
}
// 继续深入到ensureExplicitCapacity
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
3.3 SubList源码
ArrayList 使用SubList的时候并没有创建新的List,而是引用原来的List,并且把原本List的modCount复制了过来,源代码如下。
3.4 ConcurrentModificationException异常产生原因
subList在做增伤改查时,都会对比一下自己的modCount 和 原生的list的modCount,如果对应不上就会抛出ConcurrentModificationException异常,源代码如下。
// subList的get 方法 调用之前要checkForComodification
public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}
// 跟入到checkForComodification
private void checkForComodification() {
// 比较subList 的modCount 和 源list的modCount 不相等等则抛出异常
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
四. 结论和解决方案
4.1 原因总结
至此,我们在上面代码中发生异常的原因已经一目了然了,再来看一下源代码:
4.2 解决方案
为了避免出现ConcurrentModificationException异常,我们在开发时要慎用subList,可以自行使用stream来截取需要的部分。
// List newList = sourceList.subList(0, 2);
// 使用流的方式代替subList截取
List newList = sourceList.stream().skip(0).limit(2).collect(Collectors.toList());
可以对subList 进行二次封装,封装成一个新的ArrayList。
List newList = sourceList.subList(0, 2);
// 对subList的结果,再次封装成新的List
ArrayList newList1 = new ArrayList<>(newList);
五. 后话
其实上面的这个异常问题,在阿里巴巴的开发规范中早有说明,原文如下:
【强制】在 subList 场景中,高度注意对原集合元素的增加或删除,均会导致子列表的遍历、 增加、删除产生 ConcurrentModificationException 异常。
相关推荐HOT
更多>>怎么将string字符串转换成byte[]数组?
例如,使用默认字符集转换可以如下所示: 也可以指定字符集: 这将使用UTF-8字符集将字符串转换成字节数组。详情>>
2023-04-25 17:14:58如何禁用浏览器的前进和后退功能
URL) 本质就是在进入页面或者路由跳转的时候在历史记录中保存一条没有意义的记录,这样用户在点击前后后退按钮的时候就没有效果。详情>>
2023-04-19 10:36:58react传值是什么意思?
react是组件化的框架,组件实例间作用域是互相隔离的,所以组件间的通信就变成了开发过程中常常要解决的问题,根据场景可以分为: 父子组...详情>>
2023-04-18 17:23:22使用IE浏览器遇见过哪些兼容问题?
在IE6中,块元素设置float并且有水平方向的margin时,margin显示出来会比设置的值大,会导致最后一块元素被顶到下面去; 解决办法:在CSS文...详情>>
2023-04-12 09:21:12