ConcurrentModificationException和fail-fast机制

单线程下:

1. ConcurrentModificationException出现的原因

例子:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Test {
    public static void main(String[] args)  {
        List<Integer> list = new ArrayList<Integer>();
        list.add(3);
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            Integer integer = iterator.next();
            if(integer == 3)
                list.remove(integer);
        }
    }
}

运行结果:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
    at java.util.ArrayList$Itr.next(Unknown Source)
    at leetcode.Test.main(Test.java:13)

这时出现了ConcurrentModificationException,官方文档中关于这个异常的解释如下:

public class ConcurrentModificationException extends RuntimeException
This exception may be thrown by methods that have detected concurrent modification
 of an object when such modification is not permissible.
For example, it is not generally permissible for one thread to modify a Collection 
while another thread is iterating over it. In general, the results of the iteration 
are undefined under these circumstances. Some Iterator implementations (including those 
of all the general purpose collection implementations provided by the JRE) may choose
 to throw this exception if this behavior is detected. Iterators that do this are 
arbitrary, non-deterministic behavior at an undetermined time in the future.
......

程序在对 collection 进行迭代时,某个线程对该 collection 在结构上对其做了修改,这时迭代器就会抛出 ConcurrentModificationException 异常信息,从而产生 fail-fast。

Iterator是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出java.util.ConcurrentModificationException异常。

2. 解决办法

我们可先看看以从Iterator()方法的具体实现:

public Iterator<E> iterator() {
    return new Itr();
}

private class Itr implements Iterator<E> {
    int cursor = 0;
    int lastRet = -1;
    int expectedModCount = modCount;
    public boolean hasNext() {
           return cursor != size();
    }
    public E next() {
           checkForComodification();
        try {
        E next = get(cursor);
        lastRet = cursor++;
        return next;
        } catch (IndexOutOfBoundsException e) {
        checkForComodification();
        throw new NoSuchElementException();
        }
    }
    public void remove() {
        if (lastRet == -1)
        throw new IllegalStateException();
           checkForComodification();

        try {
        AbstractList.this.remove(lastRet);
        if (lastRet < cursor)
            cursor--;
        lastRet = -1;
        expectedModCount = modCount;
        } catch (IndexOutOfBoundsException e) {
        throw new ConcurrentModificationException();
        }
    }

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

这段代码不想多说,可以看到next()的checkForComodification()方法中会判断modCount和 expectedModCount的值是否相等。如果你在集合遍历过程中,删除某个元素的话,会使得modCount != expectedModCount,从而抛出ConcurrentModificationException()。

解决办法:细心的朋友会发现Itr中有remove方法,我们可以调用这个方法,实现删除元素的操作,从而不会引起ConcurrentModificationException。Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。

public class Test {
    public static void main(String[] args)  {
        List<Integer> list = new ArrayList<Integer>();
        list.add(3);
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            Integer integer = iterator.next();
            if(integer == 3)
                iterator.remove();//使用Iterator的remove方法
        }
    }
}

多线程下:

1. 出现原因

多线程下同样会出现ConcurrentModificationException(),例如:

public class Test {
    static ArrayList<Integer> list = new ArrayList<Integer>();
    public static void main(String[] args)  {
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        Thread thread1 = new Thread(){
            public void run() {
                Iterator<Integer> iterator = list.iterator();
                while(iterator.hasNext()){
                    Integer integer = iterator.next();
                    System.out.println(integer);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        };
        Thread thread2 = new Thread(){
            public void run() {
                Iterator<Integer> iterator = list.iterator();
                while(iterator.hasNext()){
                    Integer integer = iterator.next();
                    if(integer==2)
                        iterator.remove(); 
                }
            };
        };
        thread1.start();
        thread2.start();
    }
}

这段代码中thread1和thread2同时用iterator 对List对集合进行遍历,同样会产生ConcurrentModificationException()。产生的原因和单线程时是类似的,同样是两个线程的异步操作使得expectedModCount和modCount的值不一样,从而产生fail-fast。

2. 解决办法

1)在使用iterator迭代的时候使用synchronized或者Lock进行同步;
2)使用并发容器CopyOnWriteArrayList代替ArrayList和Vector。(推荐使用)

CopyOnWriterArrayList所代表的核心概念就是:任何对array在结构上有所改变的操作(add、remove、clear等),CopyOnWriterArrayList都会copy现有的数据,再在copy的数据上修改,这样就不会影响COWIterator中的数据了,修改完成之后改变原有数据的引用即可。同时这样造成的代价就是产生大量的对象,同时数组的copy也是相当有损耗的,故使用CopyOnWriterArrayList会使效率有所下降。

关于CopyOnWriteArrayList可以参照:https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CopyOnWriteArrayList.html

这部分我现在了解的不是很透彻,以后再来补充。


注意事项:

  1. Java中增强for循环(for each),底层的实现是通过Iterator迭代器模式实现的,所以它也会产生ConcurrentModificationException。

  2. 迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法:迭代器的快速失败行为应该仅用于检测 bug。


参考资料:

  1. http://www.cnblogs.com/dolphin0520/p/3933551.html
  2. http://www.hollischuang.com/archives/1776
  3. https://docs.oracle.com/javase/7/docs/api/java/util/ConcurrentModificationException.html
  4. http://blog.csdn.net/chenssy/article/details/38151189

猜你喜欢

转载自blog.csdn.net/l947069962/article/details/78070949
今日推荐