java集合 -- (6)关于集合的ConcurrentModificationException 异常

一、关于集合中ConcurrentModificationException异常

研究过java集合的人应该经常在集合源码中见到下面这段代码:

if (modCount != expectedModCount)
                throw new ConcurrentModificationException();

那么为什么会进行这样的处理呢?还有在什么情况下会触发这个异常?

对于ConcurrentModificationException,通过名字应该能猜的出来,这是一个并发修改产生的异常(这里所谓的修改是指新增或删除)。在集合中这是一种fail-fast(快速失败)机制,目的是在产生并发修改异常时主动抛出异常,而不是任其继续运行产生不可测问题。

两个属性介绍

modCount:进行修改操作的次数,初始为0。

expectedModCount:期望修改的次数,初始为0

在集合(比如ArrayList)中每次新增或删除都会执行modCount++操作

比如:

    public E remove(int index) {
        rangeCheck(index);
        //修改计数加1
        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;
    }

而在进行迭代遍历操作,获取迭代器时会将这个修改计数赋值给期望修改次数:

    private class Itr implements Iterator {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        //将修改计数赋值给期望修改次数
        int expectedModCount = modCount;
        //....

如果在迭代期间进行集合的新增删除操作,将造成modCount != expectedModCount成立。

对于迭代器iterator,我们都知道它有两个方法hasNext()和next()方法,关于next()方法:

        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];
        }

每次执行都会先执行一次checkForComodification()方法,那么这个checkForComodification方法又是干什么的呢?

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

好吧,这个方法就是本章开头我们提到的那段代码,如果修改计数和期望计数不相等则抛出异常。那么什么时候会产生这种情况呢?上面其实已经提到:

如果在迭代期间进行集合的新增删除操作,将造成modCount != expectedModCount成立。

针对这种情况,其实可以细分为两种情况:单线程和多项程

二、异常产生情景

单线程

当进行迭代时集合进行了修改操作。

例:

public class TestArray {

	public static void main(String[] args){
		final ArrayList list = new ArrayList();
		for(int i=0;i<10;i++){
			list.add(i);
		}
		Iterator iterator = list.iterator();
		while(iterator.hasNext()){
			Integer val = iterator.next();
			list.remove(val);
		}
	}
}

java集合 -- (6)关于集合的ConcurrentModificationException 异常_第1张图片

 

解决方案

1、在迭代时不使用集合的删除方法,改用迭代器的删除方法 iterator.remove();

2、使用for循环遍历集合

3、使用线程安全的集合比如CopyOnWriteArrayList

4、...

 

多线程

对于多线程,如果其中有线程在迭代遍历集合,另外还有线程同时在修改集合,那么就会产生上述异常

例如:

public class TestArray {

	public static void main(String[] args){
		final ArrayList list = new ArrayList();
		for(int i=0;i<10;i++){
			list.add(i);
		}
		new Thread(){
			@Override
			public void run() {
				
				Iterator iterator = list.iterator();
				while(iterator.hasNext()){
					System.out.println("遍历集合:"+iterator.next());
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}.start();
		new Thread(){
			@Override
			public void run() {
				Iterator iterator = list.iterator();
				while(iterator.hasNext()){
					Integer value = iterator.next();
					System.out.println("删除集合:"+value);
					iterator.remove();
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
			}
		}.start();
		
	}
}

结果:

java集合 -- (6)关于集合的ConcurrentModificationException 异常_第2张图片

上面的例子在多线程情况下,仅使用单线程遍历中进行删除的第1种解决方案使用it.remove(),但是测试得知上面1、2方案依然会产生同样的异常。
接着来再看一下JavaDoc对java.util.ConcurrentModificationException异常的描述:
当方法检测到对象的并发修改,但不允许这种修改时,抛出此异常。
说明以上办法在同一个线程执行的时候是没问题的,但是在异步情况下依然可能出现异常。

  1. 尝试方案
    (1) 在所有遍历增删地方都加上synchronized或者使用Collections.synchronizedList,虽然能解决问题但是并不推荐,因为增删造成的同步锁可能会阻塞遍历操作。
    (2) 推荐使用ConcurrentHashMap或者CopyOnWriteArrayList。

以上仅个人理解,如有错误望指正。

 

 

 

你可能感兴趣的:(java集合)