集合类单线程也能报ConcurrentModificationException?

在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();
   }
}

从上面核心代码逻辑中可以发现每次获取下一个元素之前都会检测expectedModCountmodCount是否一致,不一致则抛异常,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

总结一下:

  1. foreach遍历其实是使用了迭代器。
  2. 为了防止在迭代过程中数据被篡改,增加了两个变量,一个是迭代器内部的expectedModCount,一个是ArrayList的modCount,当两者不想等时便抛ConcurrentModificationException异常。
  3. Iterator只是一个基础版,部分类中还提供了更丰富的迭代器。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYdyKJhZ' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片