java.util.ConcurrentModificationException --fail-fast机制

Fail-fast 快速失败;fail-safe 安全失败
java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。
快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常.
//经常在迭代集合元素时,会想对集合做修改(add/remove)操作,如下 会抛出异常

for (Iterator<Integer> it = list.iterator(); it.hasNext(); ) {  
   Integer val = it.next();  
    if (val == 5) {  
        list.remove(val);  
    }  
} 

Fail-fast 是快速失败机制。Java集合的一种错误检测机制。有可能(无法对是否出现不同步并发修改做出任何硬性保证)检测出线程不安全的机制,有些不安全是不会报错的。只会best effort的形式抛出Concurrent Modification Exception。只是检测到了bug。单线程也可能会抛出此类异常。

(1)单线程环境

集合被创建后,在遍历它的过程中修改了结构。fail-fast机制在遍历一个集合时,当集合结构被修改,会抛出Concurrent Modification Exception。

(2)多线程环境

当一个线程在遍历这个集合,而另一个线程对这个集合的结构进行了修改。

  • 迭代器的遍历是直接访问内部数据,必须保证遍历过程中内部数据不被修改。迭代器内部的两个标记“expectModcount”和“modcount”,
  • ArrayList中迭代器的源代码:Iterator调用add()、clear()、remove()方法,都会改变ArrayListy元素个数都会改变modCount值,都要调用checkForComodification()方法->检测 modcount == expectcount,
private class Itr implements Iterator<E> {  
        int cursor;  
        int lastRet = -1;  
        int expectedModCount = ArrayList.this.modCount;  //在Iterator中的定义
         //modcount是全局变量,protected transient int modCount = 0; 
        public boolean hasNext() {  
            return (this.cursor != ArrayList.this.size);  
        }  
  
        public E next() {  
            checkForComodification();  
            /** 省略此处代码 */  
        }  
  
        public void remove() {  
            if (this.lastRet < 0)  
                throw new IllegalStateException();  
            checkForComodification();  
            /** 省略此处代码 */  
        }  
  
        final void checkForComodification() {  
            if (ArrayList.this.modCount == this.expectedModCount)  
                return;  
            throw new ConcurrentModificationException();  
        }  
    }  
  • 如果对正在被迭代的集合进行结构上的改变(即对该集合使用add、remove或clear方法),那么迭代器就不再合法(并且在其后使用该迭代器将会有ConcurrentModificationException异常被抛出).
  • 如果使用迭代器自己的remove方法,那么这个迭代器就仍然是合法的。

这里重点理解在遍历的同时还想删除元素时怎么做呢?(or 添加元素)

方法1:直接调用集合collection的remove()方法;(出错,)

          当集合使用Iterator进行迭代的时候,实际是new Itr()创建一个内部对象,初始化包含对象个数,可以理解为在独立线程中操作的。Iterator创建之后引用指向原来的集合对象。当原来的对象数量发生变化时,这个内部对象索引表内容其实是不会同步的。所以,当索引指针往后移动的时候就找不到要迭代的对象了。内部对象操作时为了避免这种情况都会通过checkForComodification方法检测是否一致,不一致提前抛出异常ConcurrentModifiedException。

方法2:调用Iterator的remove()方法。

           如果调用Iterator 的remove() 方法来删除的话,则iterator的内部对象个数和原来集合中对象的个数会保持同步,而直接调用集合的remove方法来删除的话,集合中对象的个数会变化而Iterator 内部对象的个数不会变化。(参考源代码)

当打开 Iterator 迭代集合时,同时又在对集合进行修改。有些集合不允许在迭代时删除或添加元素,但是调用 Iterator 的 remove() 方法是个安全的做法。


下面的源码方便理解:【https://blog.csdn.net/chenssy/article/details/38151189】

private class Itr implements Iterator<E> {  
        int cursor;  
        int lastRet = -1;  
        int expectedModCount = ArrayList.this.modCount;  
  
        public boolean hasNext() {  
            return (this.cursor != ArrayList.this.size);  
        }  
  
        public E next() {  
            checkForComodification();  
            /** 省略此处代码 */  
        }  
  
        public void remove() {  
            if (this.lastRet < 0)  
                throw new IllegalStateException();  
            checkForComodification();  
            /** 省略此处代码 */  
        }  
  
        final void checkForComodification() {  
            if (ArrayList.this.modCount == this.expectedModCount)  
                return;  
            throw new ConcurrentModificationException();  
        }  
    }  
protected transient int modCount = 0;  //全局变量


//让迭代器str implements  Iterator,调用迭代器的remove()、add()等方法。
【https://blog.csdn.net/QH_JAVA/article/details/50154405】

    public Iterator<E> iterator() {  
        return new Itr();  //它返回一个内部类。我们让这个类实现了iterator接口,如下
    }    
    private class Itr implements Iterator<E> {  
        // 表示下一个要访问的元素的索引,从next()方法的具体实现就可看出  
        int cursor;       // index of next element to return  
        // 表示上一个访问的元素的索引  ,-1表示没有
        int lastRet = -1; // index of last element returned; -1 if no such  
        // 表示对ArrayList修改次数的期望值,它的初始值为modCount。  
        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;  
            // arraylist集合底层就是一个数组,如果下一个值得检索值大于 集合的值则 报异常  
            if (i >= elementData.length)  
                throw new ConcurrentModificationException();  
            //索引值加1   
            cursor = i + 1;  
            // 返回之前的当前值的集合的值  
            return (E) elementData[lastRet = i];  
        }  
         // 删除,使用this 调用类的方法而不是内部类的方法  
        public void remove() {  
            // 如果不满足则抛出异常  
            if (lastRet < 0)  
                throw new IllegalStateException();  
            // 这个函数的作用就是检测-----expectedModCount == modCount,如果相等则不会抛出异常否则抛出异常   
            checkForComodification();  
            try {  
                //删除集合中的内容,删除的是之前的不是现在的  ,使用this
                ArrayList.this.remove(lastRet);  
                cursor = lastRet;  
                lastRet = -1;  
                // 使用iterator的方法进行删除的时候会修改expectedModCount的值为modCount 所以这样就不会出现异常  
                expectedModCount = modCount;  
            } catch (IndexOutOfBoundsException ex) {  
                throw new ConcurrentModificationException();  //因为删除之后及时修改,所以不会抛这个异常
            }  
        }  
        // 这个方法用来检查两个变量的值是否相等  
        final void checkForComodification() {  
            if (modCount != expectedModCount)  
                throw new ConcurrentModificationException();  
        }  
    }  


fail-fast的解决方法:

  1. 所有涉及改变modcount值的地方都加上同步锁,或者直接使用Collections.synchronizedList,但是增删动作的同步锁可能导致遍历被阻塞。
  2. 使用CopyOnWriteArrayList来替换ArrayList,不会抛出,,异常,不会改变原来array只会复制一个,在copy的array操作,修改完成之后改变原有数据的引用即可。所以产生大量对象。在 遍历操作数>> 增删等操作时;在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时。

    (1)需要复制集合,产生大量的无效对象,开销大

    (2)无法保证读取的数据是目前原始数据结构中的数据。


fail-fast的解决方法:

  1. 所有涉及改变modcount值的地方都加上同步锁,或者直接使用Collections.synchronizedList,但是增删动作的同步锁可能导致遍历被阻塞。
  2. 使用CopyOnWriteArrayList来替换ArrayList,不会抛出,,异常,不会改变原来array只会复制一个,在copy的array操作,修改完成之后改变原有数据的引用即可。所以产生大量对象。在 遍历操作数>> 增删等操作时;在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时。

    (1)需要复制集合,产生大量的无效对象,开销大

    (2)无法保证读取的数据是目前原始数据结构中的数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值