Java集合--foreach遍历时,不能对集合进行新增和删除(fail-fast机制)

使用foreach遍历时,不能对集合进行新增和删除

List<String> list = new ArrayList();
list.add("123");
list.add("321");
list.add("456");
list.add("789");
for (String s : list) {
    System.out.println(1);
    if ("321".equals(s)) {
        list.remove(s);
//      list.add("xxx");
    }
    System.out.println(2);
}
-------------打印如下---------------
1
2
1
2
java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:907)
	at java.util.ArrayList$Itr.next(ArrayList.java:857)

删除元素之后,在遍历下一个元素时,会报错,看一下ArrayList对于迭代器的实现

private class Itr implements Iterator<E> {
     int cursor;       // index of next element to return
     int lastRet = -1; // index of last element returned; -1 if no such
     int expectedModCount = modCount;

     public boolean hasNext() {
         return cursor != size;
     }

     @SuppressWarnings("unchecked")
     public E next() {
         checkForComodification();
         int i = cursor;
         if (i >= size)
             throw new NoSuchElementException();
         Object[] elementData = ArrayList.this.elementData;
         if (i >= elementData.length)
             throw new ConcurrentModificationException();
         cursor = i + 1;
         return (E) elementData[lastRet = i];
     }
     
     public void remove() {
         if (lastRet < 0)
             throw new IllegalStateException();
         checkForComodification();

         try {
             ArrayList.this.remove(lastRet);
             cursor = lastRet;
             lastRet = -1;
             expectedModCount = modCount;
         } catch (IndexOutOfBoundsException ex) {
             throw new ConcurrentModificationException();
         }
     }
     
	 final void checkForComodification() {
         if (modCount != expectedModCount)
             throw new ConcurrentModificationException();
     }
}

ArrayList迭代器的next方法里有一个checkForComodification方法,每次调用next方法,都会校验modCount和expectedModCount的值是否一致

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

那么modCount和expectedModCount的值又是从哪来的呢?modCount是集合的一个属性,它记录了当前集合对象被修改的次数,调用集合自身的add、remove等方法都会让modCount的值自增1,expectedModCount是迭代器的一个属性,在创建迭代器时,就会将当前modCount的值赋给expectedModCount,如果某个集合对象,modCount的值为2,在构建完迭代器后,expectedModCount也为2,当使用集合对象自身的remove方法进行删除元素后,modCount会自增1,变为3,但是并未对expectedModCount进行任何操作,所以当迭代器遍历下一个元素时,modCount的值(3)显然和expectedModCount的值(2)是不等的。

public E remove(int index) {
    rangeCheck(index);
    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
    return oldValue;
}

Java为什么会这么设计呢?

这其实是java集合的一种快速失败机制:fail-fast,快速失败的意思就是一旦检测到有异常,就立马停止工作并上报,而不是试图继续运行可能存在缺陷的系统。ArrayList的快速失败机制就是用modCount来实现的,通过检测modCount和expectedModCount的值是否相等,来表明是否有异常情况发生(产生了并发修改)。这种机制主要被用来设计在多线程并发的场景下,当多个线程同时对集合操作时,就有可能触发fail-fast,这里是可能,而不是一定,因为当线程只改变集合元素属性(只是简单的修改一个已存在元素的值),而不是改变集合结构时(添加、删除元素),是不会触发的。虽然这个机制主要被设计在多线程场景下,但是上面的例子是发生在单线程下的,说明在单线程下,也会触发这个机制,迭代器只知道在遍历的时候,集合结构发生了改变,但是不知道是单线程导致的还是多线程导致的,那么我们如果来避免单线程的这种场景呢?

  • 使用迭代器自带的添加、删除方法,看迭代器源码会发现,当用迭代器自带的方法添加/删除完元素后,会将modCount的值再次赋值给expectedModCount
  • 使用fail-safe机制的集合类,如CopyOnWriteArrayList,这里就不单独去讲它了,等以后有时间再单独出一篇CopyOnWriteArrayList的实现

– jdk版本:1.8

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值