在Java的遍历过程中经常会遇到ConcurrentModificationException
,中文名称叫并发修改异常,但是单线程怎么可能会出现这种情况呢?
Java中for循环常用的有两种遍历方式。
一种是fori:
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
另外一种是foreach:
for (String element: list) {
System.out.println(element);
}
ConcurrentModificationException
就出现在后面一种,foreach内部使用的是迭代器遍历,只要类实现了Iterable
接口,就可以使用foreach这种遍历方式。
public interface Iterable<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
default Spliterator<T> spliterator() {
return Spliterators.spliteratorUnknownSize(iterator(), 0);
}
}
上方的代码会转换为:
// 创建迭代器
Iterator<String> iterator = list.iterator();
// 如果还有下一个元素
while (iterator.hasNext()) {
// 取下一个元素
String element = iterator.next();
System.out.println(element);
}
这是一种编程思想,类内部需要自己实现迭代器,外界无需关心具体实现细节也可达到遍历的目的。
像ArrayList的迭代器是如何实现的呢?
// ArrayList中的内部类
private class Itr implements Iterator<E> {
// modeCount是ArrayList中的属性
int expectedModCount = modCount;
// 是否还有元素
public boolean hasNext() {
return cursor != size;
}
// 获取下一个元素
public E next() {
checkForComodification();
...
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
从上面核心代码逻辑中可以发现每次获取下一个元素之前都会检测expectedModCount
和modCount
是否一致,不一致则抛异常,expectedModCount
在迭代器创建的时候把modCount
赋值给它,之后就再也不会变化,那么出现这个异常的原因只能是迭代遍历过程中modCount
的值被改了。全局搜下modCount
被改动的场景。
public E remove(int index) {
rangeCheck(index);
modCount++;
...
return oldValue;
}
// 保证数组的容量,在add时一定会被调用
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
...
所以在下面这段代码中可以必现这种异常:
public class IteratorTest {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>(Arrays.asList("hello", "world", "my"));
for (String element : list) {
// 当列表中包含"my"元素时friends添加列表中
if("my".equals(element)) {
list.add("friends");
}
System.out.println(element);
}
}
}
不单单ArrayList,像其它集合类中也是采用了类似的逻辑去避免迭代过程中数据源被修改的问题,此时如果再接着迭代的话,取出来的数据是一定有问题的,索性还不如直接抛异常。
说到这里,基本也就说完了,不过还存在一个问题,如果在迭代过程中想要增删改元素该怎么办呢,就要看类的内部支不支持更丰富的迭代器,比如ArrayList中另外支持了ListIterator
。
总结一下:
- foreach遍历其实是使用了迭代器。
- 为了防止在迭代过程中数据被篡改,增加了两个变量,一个是迭代器内部的
expectedModCount
,一个是ArrayList的modCount
,当两者不想等时便抛ConcurrentModificationException
异常。 - Iterator只是一个基础版,部分类中还提供了更丰富的迭代器。
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END