在迭代List时,如果不通过iterator去修改list,那么将得到ConcurrentModificationException。
所以一般自己写的代码都会尽力避免这样的事情。但如果迭代和修改被分布在不同类的方法里,那么问题就很隐蔽了。
有一个同事写了一段这样的代码:
//代码段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。
分享到:
相关推荐
在Java编程中,`ConcurrentModificationException`是一个常见的运行时异常,通常发生在多线程环境下对集合进行迭代和修改操作时。此问题的核心在于,Java的集合类(如ArrayList、LinkedList、HashSet等)并不支持...
在Java编程中,`java.util.ConcurrentModificationException` 是一个常见的运行时异常,通常发生在尝试并发修改集合时。这个异常的产生是由于集合类(如HashMap)的非线程安全特性,当你在一个线程中使用迭代器遍历...
`java.util.ConcurrentModificationException` 是一个在 Java 中常见的运行时异常,它通常发生在多线程环境中,当一个线程正在遍历一个集合(如 `ArrayList`, `HashMap` 等),而另一个线程同时尝试修改这个集合时。...
Java并发容器CopyOnWriteArrayList是Java并发包中提供的一个并发容器,实现了线程安全且读操作无锁的ArrayList,写操作则通过创建底层数组的新副本来实现。这种读写分离的并发策略使得CopyOnWriteArrayList容器允许...
"新建 文本文档.txt"可能是文档列表中的一个文件,通常这种文件名表示未命名或初始状态的文本文件,可能包含报错大全的详细内容,如错误信息、错误原因分析和解决方法等。为了充分利用这份资源,建议打开该文件仔细...
同时,对于常见的bug,如并发修改异常(`ConcurrentModificationException`),我们需要理解其原因和避免方法,这通常涉及线程安全问题。 接着,我们转向`NIO`(New Input/Output),这是Java提供的一种I/O模型,相...
分析LinkedList的迭代器实现,可以帮助我们了解并发修改异常(ConcurrentModificationException)的原因。 总之,Java开发者不仅需要扎实的语法基础,还要熟悉相关框架和中间件的使用,同时不断学习新的技术和最佳...
此外,`CoffeeSystem`可能是一个相关的系统或者类库,但没有足够的信息来进一步分析其与`iterator()`问题的联系。 为了更好地调试和解决问题,你可以按照以下步骤操作: 1. 检查你的`import`语句。 2. 确保你的集合...
LinkedList则是一个双向链表,适合频繁进行插入和删除操作;而HashMap是一种基于哈希表的数据结构,用于存储键值对,通过键的哈希值快速定位元素。 容器类失效的常见原因包括: 1. **边界条件错误**:当我们在访问...
**解决:** 发生`ClassCastException`的原因是在尝试将一个对象强制转换为其不继承的类时。为避免此类问题: - **类型确认:** 在转换前,先通过`instanceof`关键字确认对象是否属于目标类型。 - **安全转换:** 使用...
例如,单例模式可以确保一个类只有一个实例,而工厂模式可以提供创建对象的最佳方式。 #### 三、集合与数据结构 1. **选择合适的集合类型** - 不同的集合类型适用于不同的应用场景。例如,`List`适合需要按顺序...
这是最常见的错误之一,当尝试访问一个为null的对象时抛出。确保在使用对象前已正确初始化,并在必要时进行非空检查。 2. **ArrayIndexOutOfBoundsException(数组越界异常)** 当尝试访问数组中不存在的索引时...
当多个线程同时访问一个集合时,如果一个线程修改了集合,而另一个线程正在进行迭代,则抛出`ConcurrentModificationException`异常。 **39. 说说Hashtable 与 HashMap 的区别** - **线程安全性**:`Hashtable`...
其次,线程安全性是两者之间一个关键的区别。HashTable在设计时就考虑了线程安全,它的所有方法都是同步的,因此可以在多线程环境中直接使用。相对地,HashMap是非同步的,如果要在多线程环境下使用,需要通过`...
- **原因分析**:在日期格式化中,“y”代表年份,而“Y”在某些日期格式化库中可能代表的是周年的年份,而非实际年份。为确保日期的一致性和准确性,阿里巴巴Java开发手册强调应明确指定使用“y”来表示实际年份,...
【标题】"SJSU-CS160-SurpriseError" 涉及的是一个与Java编程相关的课程项目,很可能是San Jose State University(SJSU)计算机科学160课程的一个作业或挑战。这个项目的名称暗示可能在编程过程中遇到了意外的错误...
- 集合操作:add、remove、contains等方法的实现原理,以及并发修改异常(ConcurrentModificationException)的产生原因。 3. **多线程**: - 线程的创建:通过Thread类和Runnable接口两种方式创建线程。 - 线程...