android java.util.ConcurrentModificationException

2024-02-28 20:38

本文主要是介绍android java.util.ConcurrentModificationException,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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这个异常,代码如下:

 

Java代码   收藏代码
  1. public static void main(String[] args) {  
  2.  List<String> list = new ArrayList<String>();  
  3.          list.add("1");  
  4.           list.add("2");  
  5.           list.add("3");  
  6.           list.add("4");  
  7.           list.add("5");  
  8.           list.add("6");  
  9.           list.add("7");  
  10.             
  11.  List<String> del = new ArrayList<String>();  
  12.           del.add("5");  
  13.           del.add("6");  
  14.           del.add("7");    
  15.   
  16. <span style="color: #ff0000;">for(String str : list){  
  17.      if(del.contains(str)) {  
  18.             list.remove(str);            
  19. }  
  20.           }</span>  
  21.  }  

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

 

Java代码   收藏代码
  1. Exception in thread "main" java.util.ConcurrentModificationException  

 

 

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

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

 

Java代码   收藏代码
  1. public Iterator<E> iterator() {  
  2.    return new Itr();  
  3. }  

 

 

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

 

Java代码   收藏代码
  1. private class Itr implements Iterator<E> {  
  2.     /** 
  3.      * Index of element to be returned by subsequent call to next. 
  4.      */  
  5.     int cursor = 0;  
  6.   
  7.     /** 
  8.      * Index of element returned by most recent call to next or 
  9.      * previous.  Reset to -1 if this element is deleted by a call 
  10.      * to remove. 
  11.      */  
  12.     int lastRet = -1;  
  13.   
  14.     /** 
  15.      * The modCount value that the iterator believes that the backing 
  16.      * List should have.  If this expectation is violated, the iterator 
  17.      * has detected concurrent modification. 
  18.      */  
  19.     int expectedModCount = modCount;  
  20.   
  21.     public boolean hasNext() {  
  22.             return cursor != size();  
  23.     }  
  24.   
  25.     public E next() {  
  26.             checkForComodification(); //检测modCount和expectedModCount的值!!  
  27.         try {  
  28.         E next = get(cursor);  
  29.         lastRet = cursor++;  
  30.         return next;  
  31.         } catch (IndexOutOfBoundsException e) {  
  32.         checkForComodification();  
  33.         throw new NoSuchElementException();  
  34.         }  
  35.     }  
  36.   
  37.     public void remove() {  
  38.         if (lastRet == -1)  
  39.         throw new IllegalStateException();  
  40.             checkForComodification();  
  41.   
  42.         try {  
  43.         AbstractList.this.remove(lastRet); //执行remove的操作  
  44.         if (lastRet < cursor)  
  45.             cursor--;  
  46.         lastRet = -1;  
  47.         expectedModCount = modCount; //保证了modCount和expectedModCount的值的一致性,避免抛出ConcurrentModificationException异常  
  48.         } catch (IndexOutOfBoundsException e) {  
  49.         throw new ConcurrentModificationException();  
  50.         }  
  51.     }  
  52.   
  53.     final void checkForComodification() {  
  54.         if (modCount != expectedModCount) //当modCount和expectedModCount值不相等时,则抛出ConcurrentModificationException异常  
  55.         throw new ConcurrentModificationException();  
  56.     }  
  57.     }  

 

 

再看一下ArrayList 的 remove方法

 

Java代码   收藏代码
  1. public boolean remove(Object o) {  
  2.     if (o == null) {  
  3.             for (int index = 0; index < size; index++)  
  4.         if (elementData[index] == null) {  
  5.             fastRemove(index);  
  6.             return true;  
  7.         }  
  8.     } else {  
  9.         for (int index = 0; index < size; index++)  
  10.         if (o.equals(elementData[index])) {  
  11.             fastRemove(index);  
  12.             return true;  
  13.         }  
  14.         }  
  15.     return false;  
  16.     }  
  17.   
  18.     /* 
  19.      * Private remove method that skips bounds checking and does not 
  20.      * return the value removed. 
  21.      */  
  22.     private void fastRemove(int index) {  
  23.         modCount++; //只是修改了modCount,因此modCount将与expectedModCount的值不一致  
  24.         int numMoved = size - index - 1;  
  25.         if (numMoved > 0)  
  26.             System.arraycopy(elementData, index+1, elementData, index,  
  27.                              numMoved);  
  28.         elementData[--size] = null// Let gc do its work  
  29.     }   

 

 

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

 

Java代码   收藏代码
  1. public E next() {  
  2.             checkForComodification(); //检测modCount和expectedModCount的值!!  
  3.         try {  
  4.         E next = get(cursor);  
  5.         lastRet = cursor++;  
  6.         return next;  
  7.         } catch (IndexOutOfBoundsException e) {  
  8.         checkForComodification();  
  9.         throw new NoSuchElementException();  
  10.         }  
  11.     }  
  12.    
  13. final void checkForComodification() {  
  14.         if (modCount != expectedModCount) //当modCount和expectedModCount值不相等时,则抛出ConcurrentModificationException异常  
  15.         throw new ConcurrentModificationException();  
  16.     }  
  17.     }  

 

 

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

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

 

 

解决办法如下:

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

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

 

 

Java代码   收藏代码
  1. List<String> save = new ArrayList<String>();  
  2.             
  3.           for(String str : list)  
  4.           {  
  5.            if(del.contains(str))  
  6.            {  
  7.                save.add(str);  
  8.            }  
  9.           }  
  10.           list.removeAll(save);  

 

 

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

 

Java代码   收藏代码
  1. Iterator<String> iterator = list.iterator();  
  2.      while(iterator.hasNext()) {  
  3.          String str = iterator.next();  
  4.          if(del.contains(str)) {  
  5.              iterator.remove();  
  6.          }  
  7.      }  

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

 

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

 

 

Java代码   收藏代码
  1. Iterator<String> iterator = list.iterator();    
  2. synchronized(synObject) {  
  3.  while(iterator.hasNext()) {    
  4.          String str = iterator.next();    
  5.          if(del.contains(str)) {    
  6.              iterator.remove();    
  7.          }    
  8.      }    
  9. }  
  10.       

 

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

 

 

Java代码   收藏代码
  1. final void checkForComodification() {  
  2.         if (modCount != expectedModCount)  
  3.         throw new ConcurrentModificationException();  
  4.     }  

 

 

 

 

 

 

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

 

Java代码   收藏代码
  1. Iterator<Integer> iterator = windows.keySet().iterator();  
  2.         while(iterator.hasNext()) {  
  3.             int type = iterator.next();  
  4.             windows.get(type).closeWindow();  
  5.             iterator.remove();  
  6.             windows.remove(type);   //  
  7.         }  

 

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

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

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

 

 

Java代码   收藏代码
  1. public void remove() {  
  2.             if (lastEntryReturned == null)  
  3.                 throw new IllegalStateException();  
  4.             if (modCount != expectedModCount)  
  5.                 throw new ConcurrentModificationException();  
  6.             HashMap.this.remove(lastEntryReturned.key);  
  7.             lastEntryReturned = null;  
  8.             expectedModCount = modCount; //保证了这两值的一致性  
  9.         }  

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

windows.remove(type)的源码:

 

 

Java代码   收藏代码
  1. public V remove(Object key) {  
  2.         if (key == null) {  
  3.             return removeNullKey();  
  4.         }  
  5.         int hash = secondaryHash(key.hashCode());  
  6.         HashMapEntry<K, V>[] tab = table;  
  7.         int index = hash & (tab.length - 1);  
  8.         for (HashMapEntry<K, V> e = tab[index], prev = null;  
  9.                 e != null; prev = e, e = e.next) {  
  10.             if (e.hash == hash && key.equals(e.key)) {  
  11.                 if (prev == null) {  
  12.                     tab[index] = e.next;  
  13.                 } else {  
  14.                     prev.next = e.next;  
  15.                 }  
  16.                 modCount++;  
  17.                 size--;  
  18.                 postRemove(e);  
  19.                 return e.value;  
  20.             }  
  21.         }  
  22.         return null;  
  23.     }  

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

方法,removeNullKey()源码:

 

 

Java代码   收藏代码
  1. private V removeNullKey() {  
  2.        HashMapEntry<K, V> e = entryForNullKey;  
  3.        if (e == null) {  
  4.            return null;  
  5.        }  
  6.        entryForNullKey = null;  
  7.        modCount++;  
  8.        size--;  
  9.        postRemove(e);  
  10.        return e.value;  
  11.    }  

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

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

 

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

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

这篇关于android java.util.ConcurrentModificationException的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/756553

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo