一个ConcurrentModificationException的原因分析
程序员文章站
2022-05-23 13:54:35
...
在迭代List时,如果不通过iterator去修改list,那么将得到ConcurrentModificationException。
所以一般自己写的代码都会尽力避免这样的事情。但如果迭代和修改被分布在不同类的方法里,那么问题就很隐蔽了。
有一个同事写了一段这样的代码:
拿到一个大的List,然后按照给定的大小拆分成多个小的list,再清空原有的list,然后循环多个小的list。
这货自己能产生ConcurrentModificationException吗?普通的实现下,肯定不会有问题。
但是悲剧还是出现了。
sliceList的实现导致代码段1出现ConcurrentModificationException。
原因:sliceList方法调用了list.subList,subList其实不过是原有somelist的视图,但是subList保存了somelist的modCount(修改操作的数量)。
在代码段1中,
sliceList方法之后clear方法被调用了,导致someList的modCount发生了变化。
在随后的“迭代subList进行其他处理”中,subList将被迭代,而subList不过是somelist的视图,它持有someList的modcount。
迭代subList时,迭代器的next方法将被调用,而next的第一步就是checkForComodification(),它检查someList的modCount是否与自身持有的modCount,很显然因为视图中记录了modCount之后,代码段1中clear操作已经将modCount增加了,所以不相等。
再来捋一捋:
1、sliceList方法先执行,其中的subList方法返回了someList的视图,视图记录了someList的modCount。具体参考SubList的构造方法。
2、clear方法被执行,modCount加1;
3、“迭代subList进行其他处理”开始执行,开始遍历subList方法返回的视图,该视图的next方法把自己持有的宝贝modCount与someList的modCount进行了比较。发现了不一致,抛出异常。具体参考AbstractList中Itr的next方法
综上,sliceList的实现是一个悲剧。它的实现埋下了一个隐秘的大坑。其实,在代码段2中只要subList替换成一个new一个list即可避免这样的问题。具体实现很简单。
List的具体实现可参考:AbstractList的方法subList和内部类SubList。
所以一般自己写的代码都会尽力避免这样的事情。但如果迭代和修改被分布在不同类的方法里,那么问题就很隐蔽了。
有一个同事写了一段这样的代码:
//代码段1 List<List<A>> slicedList = ListUtil.sliceList(someList,size); someList.clear(); for(List<A> subList: slicedList ){ //迭代subList进行其他处理 }
拿到一个大的List,然后按照给定的大小拆分成多个小的list,再清空原有的list,然后循环多个小的list。
这货自己能产生ConcurrentModificationException吗?普通的实现下,肯定不会有问题。
但是悲剧还是出现了。
//代码段2 public static <T> List<List<T>> sliceList(final List<T> list, int batchSize) { List<List<T>> result = new ArrayList<List<T>>(); if (CollectionUtils.isEmpty(list) || 0 >= batchSize) { return result; } final int n = (list.size() + batchSize - 1) / batchSize; for (int i = 0; i < n; i++) { result.add(list.subList(i * batchSize, Math.min((1 + i) * batchSize, list.size())));//这里 } return result; }
sliceList的实现导致代码段1出现ConcurrentModificationException。
原因:sliceList方法调用了list.subList,subList其实不过是原有somelist的视图,但是subList保存了somelist的modCount(修改操作的数量)。
在代码段1中,
sliceList方法之后clear方法被调用了,导致someList的modCount发生了变化。
在随后的“迭代subList进行其他处理”中,subList将被迭代,而subList不过是somelist的视图,它持有someList的modcount。
迭代subList时,迭代器的next方法将被调用,而next的第一步就是checkForComodification(),它检查someList的modCount是否与自身持有的modCount,很显然因为视图中记录了modCount之后,代码段1中clear操作已经将modCount增加了,所以不相等。
再来捋一捋:
1、sliceList方法先执行,其中的subList方法返回了someList的视图,视图记录了someList的modCount。具体参考SubList的构造方法。
2、clear方法被执行,modCount加1;
3、“迭代subList进行其他处理”开始执行,开始遍历subList方法返回的视图,该视图的next方法把自己持有的宝贝modCount与someList的modCount进行了比较。发现了不一致,抛出异常。具体参考AbstractList中Itr的next方法
综上,sliceList的实现是一个悲剧。它的实现埋下了一个隐秘的大坑。其实,在代码段2中只要subList替换成一个new一个list即可避免这样的问题。具体实现很简单。
List的具体实现可参考:AbstractList的方法subList和内部类SubList。
推荐阅读
-
网站打开速度慢的原因分析及对策
-
VBA中在过程中调用另一个过程失败的一种原因
-
用document.documentElement取代document.body的原因分析_javascript技巧
-
TabLayout关联ViewPager后不展示标题的原因分析
-
对于ThinkPHP框架早期版本的一个SQL注入漏洞详细分析_PHP教程
-
thinkphp的where条件无法用数组,求分析原因
-
JQuery EasyUI 加载两次url的原因分析及解决方案_jquery
-
PHP中使用mktime获取时间戳的一个黑色幽默分析_PHP
-
PHP中使用mktime获取时间戳的一个黑色幽默分析_php技巧
-
Android中EditText+Button组合导致输入板无法收起的原因分析及解决办法