android java.util.ConcurrentModificationException
1. 使用 java.util.concurrent 包下的集合
java.util.concurrent.ConcurrentHashMap;
java.util.concurrent.CopyOnWriteArrayList;
Please refer to http://www.javacodegeeks.com/2011/05/avoid-concurrentmodificationexception.html
2. 循环 list or map 时 使用 iterator 来删除被循环的元素,或者将要删除或者增加的 元素先保存在一个临时集合里,待循环结束后再一次性操作,参考: http://zhoujianghai.iteye.com/blog/1041555
以下内容转发自: http://zhoujianghai.iteye.com/blog/1041555
用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");
- <span style="color: #ff0000;">for(String str : list){
- if(del.contains(str)) {
- list.remove(str);
- }
- }</span>
- }
运行这段代码会出现如下异常:
- 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. AbstractList的next()方法
- 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)方法
把上面例子中迭代集合的代码替换成:
- 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()方法保证了modCount和expectedModCount的值的一致性,避免抛出ConcurrentModificationException异常。
不过对于在多线程环境下对集合类元素进行迭代修改操作,最好把代码放在一个同步代码块内,这样才能保证modCount和expectedModCount的值的一致性,类似如下:
- 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();会维护modCount和expectedModCount的值的一致性,而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() 方法会在删除当前迭代对象的同时维护索引的一致性。
相关推荐
Java.util.ConcurrentModificationException 异常问题详解 ConcurrentModificationException 异常是 Java 中一个常见的异常,它发生在 Iterator 遍历集合时,集合同时被修改引起的异常。在 Java 中,集合类如 ...
### Java.util.logging.Logger 使用详解 #### 一、创建Logger对象 在Java中,`java.util.logging.Logger` 是标准的日志框架之一,它提供了基础的日志记录功能。为了使用这一功能,首先需要获得 `java.util.logging...
### Java.util.Date与Java.sql.Date互转及字符串转换为日期时间格式 #### 一、Java.util.Date与Java.sql.Date的基本概念 在Java编程语言中,处理日期和时间时经常使用到`java.util.Date`和`java.sql.Date`这两个类...
### Java.util.Date与Java.sql.Date相互转换 #### 知识点概述 在Java开发中,经常需要处理日期和时间相关的操作。Java标准库提供了两个重要的日期类:`java.util.Date` 和 `java.sql.Date`。虽然它们名字相似,但...
### 使用 Java.util.zip 包实现数据压缩与解压 在计算机科学领域,数据压缩技术是一项重要的功能,它能够帮助减少存储空间的需求以及提高网络传输效率。本文将通过一系列的示例来详细介绍如何利用 Java 中的 `java....
1. java.util.concurrent - Java 并发工具包 2. 阻塞队列 BlockingQueue 3. 数组阻塞队列 ArrayBlockingQueue 4. 延迟队列 DelayQueue 5. 链阻塞队列 LinkedBlockingQueue 6. 具有优先级的阻塞队列 ...
在Java编程语言中,`java.util.InputMismatchException`是一个常见的运行时异常,它通常发生在尝试从数据源(如控制台、文件或数据库)读取数据时,遇到的数据类型与预期的不匹配。在这个特定的场景中,问题出在主线...
"java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError" 是一个典型的错误提示,它表明在并发执行过程中遇到了内存不足的问题。下面我们将深入探讨这个问题的原因、影响以及如何解决。 内存溢出...
java.util.ConcurrentModificationException 解决方法 在使用iterator.hasNext()操作迭代器的时候,如果此时迭代的对象发生改变,比如插入了新数据,或者有数据被删除。 则使用会报以下异常: Java.util....
Android util包中的Base64类,在java工程中使用可以
Java提供日期(Data)类、日历(Calendar)类,随机数(Random)类,堆栈(Stack)、向量(Vector) 、位集合(Bitset)以及哈希表(Hashtable)等类来表示相应的数据结构
在Java编程中,`java.util.ConcurrentModificationException` 是一个常见的运行时异常,通常发生在尝试并发修改集合时。这个异常的产生是由于集合类(如HashMap)的非线程安全特性,当你在一个线程中使用迭代器遍历...
Java编程语言提供了两个重要的日期处理类,分别是`java.util.Date`和`java.sql.Date`,它们在处理日期和时间上有着不同的特性和用途。 `java.util.Date`是更通用的日期时间类,它包含了日期和时间的信息,可以精确...
Java.sql.Date与Java.util.Date的区别和转换 Java.util.Date和Java.sql.Date是Java中两种不同的日期和时间表示方式,虽然它们都是表示日期和时间,但是它们之间存在着一些重要的区别。 首先,Java.util.Date是Java...
标题“java.util.pdf”暗示这是一个关于Java编程语言中util包的文档。由于描述和标签均重复标题,我们可以推断文档重点在于解释和示例展示java.util包中的类与接口。java.util是Java的标准库中的一个包,主要用于...
这是我在编写struts2中遇到的问题,整理出来,包括截图,希望可以帮到大家