`
percy30856
  • 浏览: 134481 次
  • 性别: Icon_minigender_1
  • 来自: 河南
社区版块
存档分类
最新评论

java.util.ConcurrentModificationException 解决方案

阅读更多

这是我前些日子坐开发的时候碰到的一个异常:

java.util.ConcurrentModificationException
java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
java.util.AbstractList$Itr.next(AbstractList.java:343)
com.ntcsoft.article.action.ArticleIndexAction.seaByArtType(ArticleIndexAction.java:47)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
java.lang.reflect.Method.invoke(Method.java:597)

 

解决方法:

     引用网上的解决方案:

http://jackhlp.blog.163.com/blog/static/11796218200710221172542/今天在项目的中有一个需求,需要在一个Set类型的集合中删除满足条件的对象,这时想当然地想到直接调用Set的remove(Object o)方法将指定的对象删除即可,测试代码:
   public class Test {
    public static void main(String[] args) {
        User user1 = new User();
        user1.setId(1);
        user1.setName("zhangsan");

        User user2 = new User();
        user2.setId(2);
        user2.setName("lisi");

        Set userSet = new HashSet();
        userSet.add(user1);
        userSet.add(user2);
        for (Iterator it = userSet.iterator(); it.hasNext();) {
            User user = (User) it.next();
            if (user.getId() == 1) {
                userSet.remove(user);
            }

            if (user.getId() == 2) {
                user.setName("zhangsan");
            }
        }
        for(Iterator it = userSet.iterator(); it.hasNext(); ) {
            User user = (User) it.next();
            System.out.println(user.getId() + "=>" + user.getName());
        }
    }
}
但运行程序的时候,却发现出错了:
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextEntry(Unknown Source)
    at java.util.HashMap$KeyIterator.next(Unknown Source)
    at test.Test.main(Test.java:23)

从API中可以看到List等Collection的实现并没有同步化,如果在多线程应用程序中出现同时访问,而且出现修改操作的时候都要求外部操作同步化;调用Iterator操作获得的Iterator对象在多线程修改Set的时候也自动失效,并抛出java.util.ConcurrentModificationException。这种实现机制是fail-fast,对外部的修改并不能提供任何保证。

网上查找的关于Iterator的工作机制。Iterator是工作在一个独立的线程中,并且拥有一个 mutex锁,就是说Iterator在工作的时候,是不允许被迭代的对象被改变的。Iterator被创建的时候,建立了一个内存索引表(单链表),这个索引表指向原来的对象,当原来的对象数量改变的时候,这个索引表的内容没有同步改变,所以当索引指针往下移动的时候,便找不到要迭代的对象,于是产生错误。List、Set等是动态的,可变对象数量的数据结构,但是Iterator则是单向不可变,只能顺序读取,不能逆序操作的数据结构,当 Iterator指向的原始数据发生变化时,Iterator自己就迷失了方向。

如何才能满足需求呢,需要再定义一个List,用来保存需要删除的对象:
List delList = new ArrayList();
最后只需要调用集合的removeAll(Collection con)方法就可以了。
public class Test {
    public static void main(String[] args) {
        boolean flag = false;
        User user1 = new User();
        user1.setId(1);
        user1.setName("shangsan");

        User user2 = new User();
        user2.setId(2);
        user2.setName("lisi");

        Set userSet = new HashSet();
        userSet.add(user1);
        userSet.add(user2);
        List delList = new ArrayList();
        for (Iterator it = userSet.iterator(); it.hasNext();) {
            User user = (User) it.next();
            if (user.getId() == 1) {
                delList.add(user);
            }

            if (user.getId() == 2) {
                user.setName("zhangsan");
            }
        }
        userSet.removeAll(delList);

        for(Iterator it = userSet.iterator(); it.hasNext(); ) {
            User user = (User) it.next();
            System.out.println(user.getId() + "=>" + user.getName());
        }
    }
}

 

网上的解决方法2:

http://blog.csdn.net/myyate/archive/2007/12/17/1943040.aspx

 

今天组长提了一个问题注意点,说删除Set元素的时候,如果按照一下代码则会抛出异常:     Set<Object> bb = new HashSet<Object>();
    bb.add("23242");
    bb.add(Integer.valueOf(45));
    Iterator it = bb.iterator();
    while(it.hasNext()) {
        Object ele = it.next();
        bb.remove(ele);    //wrong
    }
 异常如下:

Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
    at java.util.HashMap$KeyIterator.next(HashMap.java:827)
    at com.learn.test.Test.testIteratorRemove(Test.java:118)
    at com.learn.test.Test.main(Test.java:102)
从上可以看出异常是从HashMap的内部类抛出的。

    private abstract class HashIterator<E> implements Iterator<E> {
        Entry<K,V> next;    // next entry to return
        int expectedModCount;    // For fast-fail
        int index;        // current slot
        Entry<K,V> current;    // current entry
.....
        final Entry<K,V> nextEntry() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();   //抛出异常
            Entry<K,V> e = current = next;
            if (e == null)
                throw new NoSuchElementException();

            if ((next = e.next) == null) {
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
            return e;
        }
..........
    }
我们都知道HashSet其实只基于HashMap来实现的。看HashSet的remove实现方法:

    public boolean remove(Object o) {
    return map.remove(o)==PRESENT;
    }
再来看HashMap的remove方法:

    public V remove(Object key) {
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }
//先取得key的hashcode,根据hashcode在哈希表中找到对应的index,然后在对应的index再遍历table[i]指向的链表,这个链表一般只有一个元素
    final Entry<K,V> removeEntryForKey(Object key) {
        int hash = (key == null) ? 0 : hash(key.hashCode());
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;

        while (e != null) {
            Entry<K,V> next = e.next;
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                modCount++;
                size--;
                if (prev == e)
                    table[i] = next;
                else
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }

        return e;
    }
其实注意到其中间有这么一段代码:modCount++; modCount是这个HashMap结构修改的次数,比如增加,删除等等,再来看上面HashIterator中的expectedModCount,这个是Iterator中期望HashMap元素修改次数,初始值即modCount,HashMap的remove方法执行时,并没有同步当然也无法同步这两个变量值一致,才导致了上面的异常。找到了抛出异常的原因,很明显不能通过这个方法来删除Set中的元素,我们注意到HashIterator也有remove方法,代码如下:

        public void remove() {
            if (current == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Object k = current.key;
            current = null;
            HashMap.this.removeEntryForKey(k);
            expectedModCount = modCount;  //删除完同步两个变量的值
        }
所以尝试一下代码:

    Set<Object> bb = new HashSet<Object>();
    bb.add("23242");
    bb.add(Integer.valueOf(45));
    Iterator it = bb.iterator();
    while(it.hasNext()) {
        it.next();
        it.remove();
    }
执行成功。所以,执行HashSet的remove方法时,是不可以用Iterator方法遍历一个一个删除的。但是HashSet的remove方法也可以执行成功,就是Set中只有一个元素的时候,因为初始的时候expectedModCount和modCount是相等的。

分享到:
评论

相关推荐

    Java多线程安全集合

    虽然提供了基本的线程安全性,但它们不是高度优化的并发解决方案,因为所有操作都需要全局锁定,可能导致性能瓶颈。 2. 并发集合(Concurrent Collections): Java的`java.util.concurrent`包提供了更为高效且...

    Java 实例 - 只读集合源代码+详细指导教程.zip

    在Java编程中,只读集合(Read-Only Collection)是一种不允许进行修改的集合类型,它提供了安全的数据访问...详细指导将涵盖各种使用场景、最佳实践以及可能遇到的问题和解决方案,帮助你成为一名更优秀的Java开发者。

    Java并发编程的艺术.zip

    - **并发容器**:如`ConcurrentHashMap`、`CopyOnWriteArrayList`、`ConcurrentLinkedQueue`等,为并发访问提供了线程安全的解决方案。 - **阻塞队列**:`BlockingQueue`接口,实现生产者消费者模型,如`...

    Java 常见问题解决集合.docx

    ### Java常见问题及解决方案 #### 一、编译错误 **1.1 问题:** "cannot find symbol" **解决:** 这个错误提示通常意味着Java编译器未能找到指定的类、方法或变量。要解决这个问题,需要进行以下几个步骤的检查: ...

    关于方法iterator()找不着的问题~~~~~

    如果能查看博客的具体内容,将有助于提供更具体的解决方案。此外,`CoffeeSystem`可能是一个相关的系统或者类库,但没有足够的信息来进一步分析其与`iterator()`问题的联系。 为了更好地调试和解决问题,你可以按照...

    高级程序员必会的HashMap的线程安全问题,适用于0~2年的.7z

    本篇将深入探讨HashMap的线程安全问题,并提供相关的解决方案。 首先,我们需要了解HashMap在多线程环境下可能出现的问题: 1. **并发修改异常(ConcurrentModificationException)**:当多个线程同时修改HashMap...

    ConcurrentHashMap:今天在看Spring源码的时候发现了一个并发安全的hashmap,自己就手写实现了一下

    `ConcurrentHashMap`是Java集合框架中的一个关键成员,它在`java.util.concurrent`包下,为并发操作提供了一种高效且线程安全的解决方案。与传统的`synchronized` `HashMap`相比,`ConcurrentHashMap`采用了分段锁的...

    DataStructures:Java中的哈希表

    这个类位于Java的集合框架中,为程序员提供了灵活且性能优异的键值对存储解决方案。 **哈希表的工作原理** 哈希表基于数组和链表的组合实现。当插入一个新的键值对时,哈希函数会计算键的哈希码,然后根据这个哈希...

    高并发常见面试题(深入底层).docx

    当多个线程同时调用`add`方法时,可能会抛出`java.util.ConcurrentModificationException`异常。 **4.2 解决方案** - 使用`Vector`类代替`ArrayList`,因为`Vector`的所有公共方法都进行了同步处理,但这种方法降低...

    hashmap-thread-test:测试 Java HashMap 是否是线程安全的

    ### 解决方案 1. **Collections.synchronizedMap()**:可以使用`Collections.synchronizedMap()`静态方法将`HashMap`包装成线程安全的`SynchronizedMap`。但请注意,尽管这个方法可以确保并发修改的安全,但仍然...

    超级有影响力霸气的Java面试题大全文档

    多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。 5、String是最基本的数据类型吗?  基本数据类型包括byte、int、char、long、float、double、boolean和short。  java....

    DATASTRUCTURES-LINKEDLIST

    在Java编程语言中,链表主要体现在`LinkedList`类中,它是Java集合框架的一部分,位于`java.util`包下。 链表的基本组成部分是节点(Node),每个节点包含数据元素和指向下一个节点的引用。在Java的`LinkedList`中...

Global site tag (gtag.js) - Google Analytics