`
zhoujianghai
  • 浏览: 442260 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

java.util.ConcurrentModificationException 出现的原因和解决办法

阅读更多

 

iterator遍历集合时碰到java.util.ConcurrentModificationException这个异常,

下面以List为例来解释为什么会报java.util.ConcurrentModificationException这个异常,代码如下:

 

public static void main(String[] args) {
 List<String> list = new ArrayList<String>();
		 list.add("1");
		  list.add("2");
		  list.add("3");
		  list.add("4");
		  list.add("5");
		  list.add("6");
		  list.add("7");
		  
 List<String> del = new ArrayList<String>();
		  del.add("5");
		  del.add("6");
		  del.add("7");  

for(String str : list){
	 if(del.contains(str)) {
		    list.remove(str);		   
}
		  }
 }

 运行这段代码会出现如下异常:

 

Exception in thread "main" java.util.ConcurrentModificationException

 

 

 for(String str : list) 这句话实际上是用到了集合的iterator() 方法

 JDK java.util. AbstractList类中相关源码

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

 

 

java.util. AbstractList的内部类Itr的源码如下:

 

private class Itr implements Iterator<E> {
	/**
	 * Index of element to be returned by subsequent call to next.
	 */
	int cursor = 0;

	/**
	 * Index of element returned by most recent call to next or
	 * previous.  Reset to -1 if this element is deleted by a call
	 * to remove.
	 */
	int lastRet = -1;

	/**
	 * The modCount value that the iterator believes that the backing
	 * List should have.  If this expectation is violated, the iterator
	 * has detected concurrent modification.
	 */
	int expectedModCount = modCount;

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

	public E next() {
            checkForComodification(); //检测modCount和expectedModCount的值!!
	    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); //执行remove的操作
		if (lastRet < cursor)
		    cursor--;
		lastRet = -1;
		expectedModCount = modCount; //保证了modCount和expectedModCount的值的一致性,避免抛出ConcurrentModificationException异常
	    } catch (IndexOutOfBoundsException e) {
		throw new ConcurrentModificationException();
	    }
	}

	final void checkForComodification() {
	    if (modCount != expectedModCount) //当modCount和expectedModCount值不相等时,则抛出ConcurrentModificationException异常
		throw new ConcurrentModificationException();
	}
    }

 

 

再看一下ArrayList 的 remove方法

 

public boolean remove(Object o) {
	if (o == null) {
            for (int index = 0; index < size; index++)
		if (elementData[index] == null) {
		    fastRemove(index);
		    return true;
		}
	} else {
	    for (int index = 0; index < size; index++)
		if (o.equals(elementData[index])) {
		    fastRemove(index);
		    return true;
		}
        }
	return false;
    }

    /*
     * Private remove method that skips bounds checking and does not
     * return the value removed.
     */
    private void fastRemove(int index) {
        modCount++; //只是修改了modCount,因此modCount将与expectedModCount的值不一致
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // Let gc do its work
    } 
 

 

回过头去看看java.util. AbstractListnext()方法

 

public E next() {
            checkForComodification(); //检测modCount和expectedModCount的值!!
	    try {
		E next = get(cursor);
		lastRet = cursor++;
		return next;
	    } catch (IndexOutOfBoundsException e) {
		checkForComodification();
		throw new NoSuchElementException();
	    }
	}
 
final void checkForComodification() {
	    if (modCount != expectedModCount) //当modCount和expectedModCount值不相等时,则抛出ConcurrentModificationException异常
		throw new ConcurrentModificationException();
	}
    }

 

 

现在真相终于大白了,ArrayListremove方法只是修改了modCount的值,并没有修改expectedModCount,导致modCountexpectedModCount的值的不一致性,当next()则抛出ConcurrentModificationException异常

因此使用Iterator遍历集合时,不要改动被迭代的对象,可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护modCountexpectedModCount值的一致性。

 

 

解决办法如下:

(1)  新建一个集合存放要删除的对象,等遍历完后,调用removeAll(Collection<?> c)方法

把上面例子中迭代集合的代码替换成:

 

 

List<String> save = new ArrayList<String>();
		  
		  for(String str : list)
		  {
		   if(del.contains(str))
		   {
			   save.add(str);
		   }
		  }
		  list.removeAll(save);
 

 

   (2) 使用Iterator替代增强型for循环:

 

 Iterator<String> iterator = list.iterator();
		 while(iterator.hasNext()) {
			 String str = iterator.next();
			 if(del.contains(str)) {
				 iterator.remove();
			 }
		 }

      Iterator.remove()方法保证了modCountexpectedModCount的值的一致性,避免抛出ConcurrentModificationException异常。

 

不过对于在多线程环境下对集合类元素进行迭代修改操作,最好把代码放在一个同步代码块内,这样才能保证modCountexpectedModCount的值的一致性,类似如下:

 

Iterator<String> iterator = list.iterator();  
synchronized(synObject) {
 while(iterator.hasNext()) {  
         String str = iterator.next();  
         if(del.contains(str)) {  
             iterator.remove();  
         }  
     }  
}
    
 

因为迭代器实现类如:ListItr的next(),previous(),remove(),set(E e),add(E e)这些方法都会调用checkForComodification(),源码:

 

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

 

 

 

 

 

曾经写了下面这段对HashMap进行迭代删除操作的错误的代码:

 

Iterator<Integer> iterator = windows.keySet().iterator();
		while(iterator.hasNext()) {
			int type = iterator.next();
			windows.get(type).closeWindow();
			iterator.remove();
			windows.remove(type);	//
		}

 

 上面的代码也会导致ConcurrentModificationException的发生。罪魁祸首是windows.remove(type);这一句。

根据上面的分析我们知道iterator.remove();会维护modCountexpectedModCount的值的一致性,而windows.remove(type);这句是不会的。其实这句是多余的,上面的代码去掉这句就行了。

iterator.remove()的源码如下:HashIterator类的remove()方法

 

 

 public void remove() {
	            if (lastEntryReturned == null)
	                throw new IllegalStateException();
	            if (modCount != expectedModCount)
	                throw new ConcurrentModificationException();
	            HashMap.this.remove(lastEntryReturned.key);
	            lastEntryReturned = null;
	            expectedModCount = modCount; //保证了这两值的一致性
	        }

 HashMap.this.remove(lastEntryReturned.key);这句代码说明windows.remove(type);是多余的,因为已经删除了该key对应的value。

windows.remove(type)的源码:

 

 

public V remove(Object key) {
        if (key == null) {
            return removeNullKey();
        }
        int hash = secondaryHash(key.hashCode());
        HashMapEntry<K, V>[] tab = table;
        int index = hash & (tab.length - 1);
        for (HashMapEntry<K, V> e = tab[index], prev = null;
                e != null; prev = e, e = e.next) {
            if (e.hash == hash && key.equals(e.key)) {
                if (prev == null) {
                    tab[index] = e.next;
                } else {
                    prev.next = e.next;
                }
                modCount++;
                size--;
                postRemove(e);
                return e.value;
            }
        }
        return null;
    }

 上面的代码中,由于先调用了iterator.remove();所以再调用HashMap的remove方法时,key就已经为null了,所以会执行:removeNullKey();

方法,removeNullKey()源码:

 

 

 private V removeNullKey() {
        HashMapEntry<K, V> e = entryForNullKey;
        if (e == null) {
            return null;
        }
        entryForNullKey = null;
        modCount++;
        size--;
        postRemove(e);
        return e.value;
    }

 不过不管是执行removeNullKey()还是key != null,如果直接调用HashMap的remove方法,都会导致ConcurrentModificationException

这个异常的发生,因为它对modCount++;没有改变expectedModCount的值,没有维护维护索引的一致性。

 

下面引用一段更专业的解释:

Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。
所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。

 

分享到:
评论
2 楼 qja 2015-02-05  
Iterator.remove()
这个方法也会出错的。
1 楼 angeli 2014-11-19  
List<String> save = new ArrayList<String>(); 
           
          for(String str : list) 
          { 
           if(del.contains(str)) 
           { 
               save.add(str); 
           } 
          } 
          list.removeAll(save);  这个方法也是会出错的

相关推荐

    java.util.ConcurrentModificationException 异常问题详解1

    Java.util.ConcurrentModificationException 异常问题详解 ConcurrentModificationException 异常是 Java 中一个常见的异常,它发生在 Iterator 遍历集合时,集合同时被修改引起的异常。在 Java 中,集合类如 ...

    java.util.ConcurrentModificationException 解决方法

    java.util.ConcurrentModificationException 解决方法 在使用iterator.hasNext()操作迭代器的时候,如果此时迭代的对象发生改变,比如插入了新数据,或者有数据被删除。 则使用会报以下异常: Java.util....

    出现java.util.ConcurrentModificationException 问题及解决办法

    在Java编程中,`java.util.ConcurrentModificationException` 是一个常见的运行时异常,通常发生在尝试并发修改集合时。这个异常的产生是由于集合类(如HashMap)的非线程安全特性,当你在一个线程中使用迭代器遍历...

    java.util.ConcurrentModificationException(解决方案).md

    Java提供了几种解决方案来应对这种情况,以下是详细的解决方法和知识点: ### 并发集合的使用 Java的`java.util.concurrent`包中提供了多种线程安全的集合类,如`ConcurrentHashMap`、`CopyOnWriteArrayList`等,...

    java.util.concurrent系列文章(1)

    ### Java.util.concurrent ...本文详细介绍了 `java.util.concurrent` 包中 `ConcurrentHashMap` 和 `CopyOnWriteArrayList` 的使用和特点,对于希望深入了解 Java 并发编程的开发者来说是一篇非常有价值的参考文献。

    java.util介绍.pdf

    `java.util`包中的`Collection`接口和其子接口(如`List`、`Set`)以及具体的实现类(如`ArrayList`、`LinkedList`等)构成了Java中处理数据集合的核心框架。了解它们的特性和使用方法对于编写高效、可靠的Java程序...

    java 集合并发操作出现的异常ConcurrentModificationException

    在Java编程中,`ConcurrentModificationException`是一个常见的运行时异常,主要出现在多线程环境下对集合类(如List、Set、Map等)进行并发修改时。然而,这个异常不仅限于多线程环境,即使在单线程中,如果在遍历...

    com.harmonyos4.exception.ConcurrentModificationException.md

    import java.util.concurrent.ConcurrentModificationException; public class ConcurrentModificationExample { public static void main(String[] args) { List&lt;String&gt; list = new ArrayList(); // 使用...

    Java语言的Util类详细介绍

    Java语言的Util类详细介绍 Java语言的Util类是Java开发中非常重要的一部分,它提供了一系列的类来实现基本的数据结构,如线性表、链表等。这些类均在java.util包中。 Collection接口是Java中最基本的集合接口,一...

    ConcurrentModificationException如何解决.md

    在多线程环境中,处理集合的迭代和修改操作时,必须注意`ConcurrentModificationException`异常的出现。我们可以选择使用线程安全的集合类,或者是通过同步机制保护关键代码块,以确保线程安全。这两种方法各有适用...

    ConcurrentModificationException解决办法.md

    ConcurrentModificationException异常是在Java编程中处理集合时经常会遇到的一个问题。当集合正在被迭代时,如果...希望这些方法能够帮助你有效解决在Java集合操作中遇到的ConcurrentModificationException异常问题。

    ConcurrentModificationException(解决方案).md

    选择合适的解决策略,可以有效避免 ConcurrentModificationException,确保程序的稳定性和数据的一致性。 无论选择哪种方法,理解并发编程中集合的迭代和修改问题,以及掌握处理 ConcurrentModificationException ...

    spring-data-mongodb-test:在Collections.synchronizedList或Collections.synchronizedSet上测试spring数据mongodb ConcurrentModificationException

    Spring数据mongodb测试 在Collections.synchronizedList或Collections.synchronizedSet上测试spring数据mongodb ConcurrentModificationException

    JAVA.BUG模式详解

    本篇文章将深入探讨几个常见的JAVA.BUG模式,并提供相应的解决策略和优化技巧。 一、空指针异常(NullPointerException) 这是Java中最常见的错误之一,当尝试访问一个为null的对象的成员时,程序会抛出此异常。...

    遍历并批量删除容器中元素出现ConcurrentModificationException原因及处置

    下面详细解析一下这个问题的原因和解决策略: 1. **原因分析**: - 当你使用`for-each`循环(增强for循环)遍历集合时,实际上底层是通过迭代器实现的。但是,如果你在循环内部直接修改集合(比如删除元素),而...

    ConcurrentModificationException.md

    1. ConcurrentModificationException是一个在Java中多线程环境下修改集合时可能出现的异常,它通常发生在使用迭代器遍历集合的过程中,如果有其他线程或代码块修改了集合的内容,则可能会抛出该异常。 2. 解决该...

    jdk 1.6 API 中文版帮助文档

    - `java.util.Iterator`的改进:支持`remove()`操作,避免抛出`ConcurrentModificationException`。 ### 4. 性能优化 JDK 1.6对编译器和垃圾收集器进行了优化,提高了运行效率,例如: - **Server VM的改进**: ...

    Java多线程安全集合

    例如`Collections.unmodifiable*`方法创建的集合和`java.util.Collections`类中的`emptyList()`、`emptySet()`等。这些集合一旦创建就不能修改,因此天然线程安全。 示例代码: ```java import java.util.*; ...

Global site tag (gtag.js) - Google Analytics