`
darrenzhu
  • 浏览: 802647 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

ConcurrentModificationException, clone, Collections.copy

    博客分类:
  • Java
阅读更多
注意,这里的参考文章基本来自Effective Java和jdk源码

1)ConcurrentModificationException
当你用for each遍历一个list时,如果你在循环主体代码中修改list中的元素,将会得到这个Exception,解决的办法是:
1)用listIterator, 它支持在遍历的过程中修改元素,
2)不用listIterator, new一个新的list,copy old list content to new list, and then modify the new created list, in this way we can avoid such expception.
注意Collection中只有iterator,没有listIterator,List接口中才有listIterator.

ListIterator<FilterCriteriaVO> listIterator=filterVOs.listIterator();
int size=filterVOs.size();
for (int i=0; i<size; i++) {
   FilterCriteriaVO filterExpression=listIterator.next();
  listIterator.remove();
  listIterator.add(leg1TypeExpression);
}

请注意,listIterator的remove方法是不需要参数的,因为它是针对最近一次由next或previous获取的对象而言的.
void java.util.ListIterator.remove()
Removes from the list the last element that was returned by next or previous (optional operation). This call can only be made once per call to next or previous. It can be made only if ListIterator.add has not been called after the last call to next or previous.

事实上,不仅只有remove方法这样,ListIterator里面的add,set方法也是基于next和previous返回的结果的。详情可以参考jdk源代码。


扩展知识:
java.util.ConcurrentModificationException
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 known as fail-fast iterators, as they fail quickly and cleanly, rather that risking arbitrary, non-deterministic behavior at an undetermined time in the future.

Note that this exception does not always indicate that an object has been concurrently modified by a different thread. If a single thread issues a sequence of method invocations that violates the contract of an object, the object may throw this exception. For example, if a thread modifies a collection directly while it is iterating over the collection with a fail-fast iterator, the iterator will throw this exception.

Note that fail-fast behavior cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast operations throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs.

java.util.ListIterator<E>
An iterator for lists that allows the programmer to traverse the list in either direction, modify the list during iteration, and obtain the iterator's current position in the list. A ListIterator has no current element; its cursor position always lies between the element that would be returned by a call to previous() and the element that would be returned by a call to next(). An iterator for a list of length n has n+1 possible cursor positions, as illustrated by the carets (^) below:

Element(0)  ^ Element(1) ^  Element(2)   ... Element(n-1)^cursor positions:  ^         
Note that the remove and set(Object) methods are not defined in terms of the cursor position; they are defined to operate on the last element returned by a call to next or previous().
This interface is a member of the Java Collections Framework.

2)关于clone以及Cloneable接口
Cloneable里面没有包含任何方法,它只是决定了Object中受保护的clone方法实现的行为:如果一个类实现了Cloneable, Object的clone方法就返回该对象的逐域拷贝,否则就抛出CloneNotSupportedException。这是接口的一种极端非典型的用法,不值得效仿。通常情况下,实现接口是为了表明类可以为它的客户做些什么。然而对应Cloneable接口,它改变了超类中受保护的方法的行为。
Best Practise:
1)最好不要去实现自己的clone方法,即不要去implement Cloneable接口,要想拷贝,自己可以写一个copy方法,所有的内容完全由自己控制。
2)如果一定要实现Cloneable,那么首先通过super.clone()获取到最初始的拷贝对象,然后去覆盖一些域,因为对list而言,通过super.clone()方法获得的拷贝对象只是在栈中新建了一个list引用变量,但是这个引用变量还是指向原来list中的成员的,即并没有在堆中新建list的成员。

先看一下Jdk1.6 中HashMap的clone方法源码:
    /**
     * Returns a shallow copy of this <tt>HashMap</tt> instance: the keys and
     * values themselves are not cloned.
     *
     * @return a shallow copy of this map
     */
    public Object clone() {
        HashMap<K,V> result = null;
	try {
	    result = (HashMap<K,V>)super.clone();
	} catch (CloneNotSupportedException e) {
	    // assert false;
	}
        result.table = new Entry[table.length];
        result.entrySet = null;
        result.modCount = 0;
        result.size = 0;
        result.init();
        result.putAllForCreate(this);

        return result;
    }

java.lang.Cloneable
A class implements the Cloneable interface to indicate to the java.lang.Object.clone() method that it is legal for that method to make a field-for-field copy of instances of that class.

Invoking Object's clone method on an instance that does not implement the Cloneable interface results in the exception CloneNotSupportedException being thrown.

By convention, classes that implement this interface should override Object.clone (which is protected) with a public method. See java.lang.Object.clone() for details on overriding this method.

Note that this interface does not contain the clone method. Therefore, it is not possible to clone an object merely by virtue of the fact that it implements this interface. Even if the clone method is invoked reflectively, there is no guarantee that it will succeed.

3)Collections.copy

List<Object> dest=new ArrayList<Object>(src.size());
//or List<Object> dest=new ArrayList<Object>();
Collections.copy(dest, src());


按如上方式用时,很容易得到"Source does not fit in dest" Exception,原因是copy方法里面会首先比较dest和src的size,而往往你新new的list的size是0,所以会报错。
为什么明明已经设置了长度为src.size,为什么还会出错!
后来打印出dest.size()才知道dest的长度为0,new ArrayList<Object>(src.size())表示的是这个List的容纳能力为src.size,并不是说dest中就有了src.size个元素。查看api才知 道,它的capacity(容纳能力大小)可以指定(最好指定)。而初始化时size的大小永远默认为0,只有在进行add和remove等相关操作 时,size的大小才变化。然而进行copy()时候,首先做的是将desc1的size和src1的size大小进行比较,只有当desc1的 size 大于或者等于src1的size时才进行拷贝,否则抛出IndexOutOfBoundsException异常。

可以通过下面的方法指定目标desc的大小
List<Object> values=new ArrayList<Object>(Arrays.asList(new Object[srcList.size()]));
Collections.copy(values, srcList());

执行第一句后,desc 的size的大小是为srcList的size,其实它是对一个空数组的浅拷贝,虽然是一个Object类型的空数组,但是list里面只存储引用变量,所以list的size已经变化了,只是每个引用变量还没有指向具体的对象而已。


Collections.copy源码
    /**
     * Copies all of the elements from one list into another.  After the
     * operation, the index of each copied element in the destination list
     * will be identical to its index in the source list.  The destination
     * list must be at least as long as the source list.  If it is longer, the
     * remaining elements in the destination list are unaffected. <p>
     *
     * This method runs in linear time.
     *
     * @param  dest The destination list.
     * @param  src The source list.
     * @throws IndexOutOfBoundsException if the destination list is too small
     *         to contain the entire source List.
     * @throws UnsupportedOperationException if the destination list's
     *         list-iterator does not support the <tt>set</tt> operation.
     */
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        int srcSize = src.size();
        if (srcSize > dest.size())
            throw new IndexOutOfBoundsException("Source does not fit in dest");

        if (srcSize < COPY_THRESHOLD ||
            (src instanceof RandomAccess && dest instanceof RandomAccess)) {
            for (int i=0; i<srcSize; i++)
                dest.set(i, src.get(i));
        } else {
            ListIterator<? super T> di=dest.listIterator();
	    ListIterator<? extends T> si=src.listIterator();
            for (int i=0; i<srcSize; i++) {
                di.next();
                di.set(si.next());
            }
        }
    }


4)Collections.copy() versus ArrayList(Collection<? extends E> c)

两者都是浅拷贝,只是对源list的元素进行拷贝,拷贝的只是引用。拷贝后两个list的元素(引用)不同,但是引用所指向的对象是一样的。即两个list的每个元素指向的还是同一内存。
分享到:
评论
2 楼 darrenzhu 2014-05-30  
Checkmate 写道
最后一条我倒是看到了不同的解释.
Both methods are shallow copy. So what is the difference between these two methods?

First, Collections.copy() won’t reallocate the capacity of dstList even if dstList does not have enough space to contain all srcList elements. Instead, it will throw an IndexOutOfBoundsException. One may question if there is any benefit of it. One reason is that it guarantees the method runs in linear time. Also it makes suitable when you would like to reuse arrays rather than allocate new memory in the constructor of ArrayList.

Collections.copy() can only accept List as both source and destination, while ArrayList accepts

我也觉得貌似都是浅克隆.


you are right, 都是浅拷贝.
1 楼 Checkmate 2013-09-26  
最后一条我倒是看到了不同的解释.
Both methods are shallow copy. So what is the difference between these two methods?

First, Collections.copy() won’t reallocate the capacity of dstList even if dstList does not have enough space to contain all srcList elements. Instead, it will throw an IndexOutOfBoundsException. One may question if there is any benefit of it. One reason is that it guarantees the method runs in linear time. Also it makes suitable when you would like to reuse arrays rather than allocate new memory in the constructor of ArrayList.

Collections.copy() can only accept List as both source and destination, while ArrayList accepts

我也觉得貌似都是浅克隆.

相关推荐

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

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

    java.util.ConcurrentModificationException 解决方法

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

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

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

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

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

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

    4. 或者,可以使用`Collections.synchronizedMap()`或`CopyOnWriteArrayList/CopyOnWriteArraySet`等同步集合类,它们在多线程环境下提供了一定程度的安全性。 总之,`ConcurrentModificationException`是Java集合...

    axis1.4.1.zip

    标题“axis1.4.1.zip”所指的是一份针对Axis1.4版本的修复补丁包,这个补丁主要是为了解决在Java Development Kit (JDK) 1.8环境下,高并发场景下出现的`ConcurrentModificationException`问题。`...

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

    在多线程环境中,只读集合特别有用,因为它们能防止并发修改异常(`ConcurrentModificationException`)。当多个线程试图同时修改集合时,可能会出现这种问题。只读集合确保了即使在并发环境下,数据也能保持一致。 ...

    Java多线程安全集合

    - `CopyOnWriteArrayList`和`CopyOnWriteArraySet`:这些列表和集在线程安全的迭代器上有优势,因为它们在修改时复制底层数组,从而避免了迭代过程中的并发修改异常(`ConcurrentModificationException`)。...

    java 面试题目

    当多个线程同时访问一个集合时,可能会抛出`ConcurrentModificationException`。面试官可能会问如何避免这种异常,一种解决方案是使用`CopyOnWriteArrayList`或`ConcurrentHashMap`等并发安全的集合类。 #### 4. ...

    java基础之集合面试题共4页.pdf.zip

    14. **集合的复制**:通过clone()、Arrays.copyOf() 或 Collection.clone() 方法可以实现集合的浅复制。深复制涉及对象的复杂性,需要额外的处理。 15. **集合的排序**:Collections.sort() 方法可以对List进行排序...

    Java《面向对象程序设计》实验报告五

    迭代器在遍历过程中删除元素时,不会导致ConcurrentModificationException。 2. List接口的使用与排序: - 第二部分实验要求将一系列数字存入List中,并进行升序和降序排序。使用`ArrayList`实现List接口,通过`...

    java集合框架PPT

    `Iterator`在遍历过程中可以删除元素,但不建议在遍历期间修改集合,因为这可能导致`ConcurrentModificationException`。 5. **增强型for循环(foreach)**:简化了集合的遍历,如`for (Element e : collection) {....

    Java的线程安全与不安全集合.docx

    在并发环境下,多个线程同时调用`ArrayList`的`add()`方法会导致`ConcurrentModificationException`,因为这些操作没有内置的同步控制。 2. `LinkedList`: 基于链表结构的列表,它的插入和删除操作通常比`ArrayList...

    Java Collections Interview Questions.pdf

    fail-fast 迭代器在集合修改时,会抛出 ConcurrentModificationException 异常。fail-safe 迭代器在集合修改时,会返回当前集合的快照。 RandomAccess 接口的应用场景 RandomAccess 接口是 Java Collections 框架...

    Java集合多线程安全.docx

    例如,在多个线程同时尝试修改列表时,可能会抛出`ConcurrentModificationException`。 2. `LinkedList`:与`ArrayList`类似,其迭代器也不是线程安全的,因此并发修改会导致异常。 3. `HashMap`:并发写入可能导致...

    阿里巴巴Java开发手册

    - 使用`Collections.emptyXX()`或`Collections.emptyList()()`创建不可变的空集合。 - 避免在循环中调用`list.add()`等改变集合大小的方法,可能导致 ConcurrentModificationException。 6. **并发编程**: - ...

    30个Java经典的集合面试题!.zip

    如果在迭代过程中集合结构被修改(如添加、删除元素),迭代器会抛出ConcurrentModificationException。 28. **什么是Java的并发容器?** 如ConcurrentHashMap、ConcurrentLinkedQueue等,它们设计用于并发环境,...

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

    - 如果在多线程环境中,需要使用线程安全的数据结构,如`Collections.synchronizedList()`、`CopyOnWriteArrayList`、`ConcurrentHashMap`等,它们支持并发修改。 - 使用`synchronized`关键字或者`java.util....

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

    1. 使用`Collections.synchronizedXXX`方法:如`synchronizedList`, `synchronizedSet`等,它们会返回一个线程安全的集合包装器。不过,虽然外部访问是同步的,但迭代过程中的修改仍然需要额外的同步控制,以避免...

Global site tag (gtag.js) - Google Analytics