• 集合操作出现的ConcurrentModificationException(源码分析)


    摘要:
    为了保证线程安全,在迭代器迭代的过程中,线程是不能对集合本身进行操作(修改,删除,增加)的,否则会抛出ConcurrentModificationException的异常。 
    示例:
     1  public static void main(String[] args) {
     2         Collection num = new ArrayList<String>();
     3         num.add("One");
     4         num.add("Two");
     5         num.add("Three");
     6 
     7         Iterator<String> iterator= num.iterator();
     8         while(iterator.hasNext()){
     9             String element = iterator.next();
    10             if("One".equals(element)){
    11                num.remove(element);
    12             }else {
    13                 System.out.println(element);
    14             }
    15         }
    16     }
    17 }
    运行结果:
    Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
    at java.util.ArrayList$Itr.next(ArrayList.java:831)
    at com.mobin.thread.IterationTest.main(IterationTest.java:20) //错误代码对应上面的11行
    源码分析
    先来分析下整个代码的执行流程:
     
    集合操作中有两个比较重要的指标:modCount和exceptedModCount。
     
    modCount值是用来记录当前集合被修改的次数,每修改一次就进行加1(可以认为为集合的版本号),而exceptedModCount是在iterator初始化是就已经指定的值,值为exceptedModCount = modCount,对于上面的代码,开始就对集合进行了三次add操作,所以modCount=3,当代码执行到第9行时就创建了iterator对象执行exceptedModCount = modCount语句对exceptedModCount进行了赋值操作,此时exceptedModCount=3,具体过程如下:
    当执行到while语句时,会对iterator.hasnext()进行判断真假,hasnext访方法实现如下:
    public boolean hasNext() {
       return cursor != size;
    }
    cursor又是什么呢?
         cursor是访问集合时指向元素的游标,开始为0,每执行next语句一次就+1,所以当访问到最后一个元素时cursor就等于size,此时就会结束循环。
    之后执行next语句,next具体实现如下:
     1  public E next() {
     2             checkForComodification();
     3             int i = cursor;
     4             if (i >= size)
     5                 throw new NoSuchElementException();
     6             Object[] elementData = ArrayList.this.elementData;
     7             if (i >= elementData.length)
     8                 throw new ConcurrentModificationException();
     9             cursor = i + 1;
    10             return (E) elementData[lastRet = i];
    11   }
    接着代码进入if语句执行了remove操作,modCount的值+1,这时再一次循环while语句进入next语句并执行next里的checkForComodifcation方法对modCount和exceptedModCount的值进行判断,此时modCount!= exceptedModCount就抛出了开始的那个异常,这就是报错的原因。
     
    当把if语句里的条件改下面代码又会怎样呢?
    if("Two".equals(element)){ //条件发生了改变,对第二个元素进行判断
       num.remove(element);
    }else {
       System.out.println(element);
    }
    运行结果:
    One
    只输出了一个值就结束程序了,并没有报错!
     
    当执行第一遍while语句时,执行了next方法,cursor值+1,cursor=1,并输出One
     
    当执行第二遍while语句时,执行了next方法,cursor值+1,cursor=2,并删除了Two这个元素,size也为2,
     
    还记得是怎样判断while条件真假的吗?
    1 public boolean hasNext() {
    2 return cursor != size;
    3 }
    所以当执行第三遍while语句时,cursor = size,hasnext返回false,结束循环,所以程序“来不及”报错就退出循环了,最后只输出一个元素。
     
    解决办法
    那么又如何在迭代集合时对集合进行操作呢?
    方法一:
    使用CopyOnWriteArrayList,而不是ArrayList
    Collection num = new CopyOnWriteArrayList();
    原理:
    CopyOnWriteArrayList会先复制一个集合副本,当对集合进行修改时普遍的上修改的是副本里的值,修改完后再将原因集合的引用指向这个副本,避免抛出ConcurrentModificationException异常,使用了CopyOnWriteArrayList,多线程可以同时对这个容器进行迭代,而不会干扰或者与修改容器的线程相互干扰
    底层源码实现如下:
     1 public boolean add(E e) {
     2    final ReentrantLock lock = this.lock; //重入锁
     3    lock.lock(); //加锁,效果与synchronized一样
     4 try {
     5    Object[] elements = getArray();
     6    int len = elements.length; //得到数组长度
     7    Object[] newElements = Arrays.copyOf(elements, len + 1); //复制元素到一个新数组里
     8    newElements[len] = e; //原数组指向新数组
     9    setArray(newElements);
    10    return true;
    11 } finally {
    12    lock.unlock(); //在finally里解锁是为了避免发生死锁
    13 }
    14 }
    方法二:
    使用iterator对象的remove方法
    if("Two".equals(element)){
        iterator.remove(); //修改了此行
    }else {
        System.out.println(element);
    }

     

  • 相关阅读:
    03- CSS进阶
    03-requests使用
    04-scrapy简介
    05-scrapy基本使用
    06-CrawlSpider模板
    07-Request、Response
    03-inotify+rsync sersync lsyncd实时同步服务
    markdown中折叠代码
    02-java基础语法
    01-java简介及环境配置
  • 原文地址:https://www.cnblogs.com/MOBIN/p/5340735.html
Copyright © 2020-2023  润新知