`

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

 
阅读更多
用iterator遍历集合时碰到java.util.ConcurrentModificationException这个异常,
下面以List为例来解释为什么会报java.util.ConcurrentModificationException这个异常,代码如下:
 
Java代码  
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");    
  
<span style="color: #ff0000;">for(String str : list){  
     if(del.contains(str)) {  
            list.remove(str);            
}  
          }</span>  
 }  
 运行这段代码会出现如下异常:
 
Java代码  
Exception in thread "main" java.util.ConcurrentModificationException  
 
 
 for(String str : list) 这句话实际上是用到了集合的iterator() 方法
 JDK java.util. AbstractList类中相关源码
Java代码  
public Iterator<E> iterator() {  
   return new Itr();  
}  
 
 
java.util. AbstractList的内部类Itr的源码如下:
 
Java代码  
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方法
 
Java代码  
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. AbstractList的next()方法
 
Java代码  
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();  
    }  
    }  
 
 
现在真相终于大白了,ArrayList的remove方法只是修改了modCount的值,并没有修改expectedModCount,导致modCount和expectedModCount的值的不一致性,当next()时则抛出ConcurrentModificationException异常
因此使用Iterator遍历集合时,不要改动被迭代的对象,可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护modCount和expectedModCount值的一致性。
 
 
解决办法如下:
(1)  新建一个集合存放要删除的对象,等遍历完后,调用removeAll(Collection<?> c)方法
把上面例子中迭代集合的代码替换成:
 
 
Java代码  
List<String> save = new ArrayList<String>();  
            
          for(String str : list)  
          {  
           if(del.contains(str))  
           {  
               save.add(str);  
           }  
          }  
          list.removeAll(save);  
 
 
   (2) 使用Iterator替代增强型for循环:
 
Java代码  
Iterator<String> iterator = list.iterator();  
     while(iterator.hasNext()) {  
         String str = iterator.next();  
         if(del.contains(str)) {  
             iterator.remove();  
         }  
     }  
      Iterator.remove()方法保证了modCount和expectedModCount的值的一致性,避免抛出ConcurrentModificationException异常。
 
不过对于在多线程环境下对集合类元素进行迭代修改操作,最好把代码放在一个同步代码块内,这样才能保证modCount和expectedModCount的值的一致性,类似如下:
 
Java代码  
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(),源码:
 
Java代码  
final void checkForComodification() {  
        if (modCount != expectedModCount)  
        throw new ConcurrentModificationException();  
    }  
 
 
 
 
 
 
曾经写了下面这段对HashMap进行迭代删除操作的错误的代码:
 
Java代码  
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();会维护modCount和expectedModCount的值的一致性,而windows.remove(type);这句是不会的。其实这句是多余的,上面的代码去掉这句就行了。
iterator.remove()的源码如下:HashIterator类的remove()方法
 
 
Java代码  
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)的源码:
 
 
Java代码  
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()源码:
 
 
Java代码  
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() 方法会在删除当前迭代对象的同时维护索引的一致性。
 

 

分享到:
评论

相关推荐

    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.util介绍.pdf

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

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

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

    Java语言的Util类详细介绍

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

    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循环)遍历集合时,实际上底层是通过迭代器实现的。但是,如果你在循环内部直接修改集合(比如删除元素),而...

    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.*; ...

    java8集合源码-zinc-ConcurrentModificationException:锌并发修改异常

    java.util.ConcurrentModificationException: mutation occurred during iteration [error] scala.collection.mutable.MutationTracker$.checkMutations(MutationTracker.scala:43) [error] scala.collection....

    多线程中使用Java集合类.doc

    3. 使用`java.util.concurrent.ConcurrentHashMap`代替HashMap,它在并发环境下提供高效且线程安全的访问和修改。 4. 如果需要更复杂的并发控制,可以使用`java.util.concurrent.locks`包下的Lock接口及其实现,如...

    迭代大师的修炼之道:Java中Iterator与增强for循环的深度解析

    ### 迭代大师的修炼之道:Java中Iterator与Enhanced for loop的深度解析 #### 一、引言 在Java编程的世界里,迭代是处理集合数据的重要手段之一。本文将重点探讨两种常用的迭代方式——`Iterator`接口和`Enhanced ...

    Java源码分析:深入探讨Iterator模式

    在Java编程语言中,集合框架(`java.util`包)提供了多种容器类来存储对象,如`List`、`Set`和`Map`等。为了遍历这些容器中的元素,Java引入了迭代器模式(Iterator Pattern),这是一种常用的设计模式,它提供了一...

    concurrent.rar

    9. **并发异常处理**:在并发编程中,死锁、活锁、饥饿等是常见的问题,理解这些异常并学会避免和解决是提升并发编程能力的关键。 10. **并发编程最佳实践**:包括避免过度同步,减少锁粒度,正确使用线程池,以及...

Global site tag (gtag.js) - Google Analytics