zoukankan      html  css  js  c++  java
  • foreach + remove = ConcurrentModificationException

    foreach + remove = ConcurrentModificationException


    问题:

    List<String> list = new ArrayList<>();
    list.add("0");
    list.add("1");
    list.add("2");
    list.add("3");
    for (String s : list) {
        if (条件语句) {
            list.remove(s);
        }
    }
    

    在上述代码的 if 语句中依次使用下列条件语句并执行代码:

    条件语句 执行结果
    "0".equals(s) 抛出 java.util.ConcurrentModificationException 异常
    "1".equals(s) 抛出 java.util.ConcurrentModificationException 异常
    "2".equals(s) 正常执行
    "3".equals(s) 抛出 java.util.ConcurrentModificationException 异常

    分析:

    对代码进行反编译,可以发现 foreach 只是一个语法糖,最终需要转换为 iterator 进行实现:

    ArrayList<String> list = new ArrayList<String>();
    list.add("0");
    list.add("1");
    list.add("2");
    list.add("3");
    Iterator iterator = list.iterator();
    while (iterator.hasNext()) {
        String s = (String)iterator.next();
        if (!"2".equals(s)) continue;
        list.remove(s);
    }
    

    ArrayList 存在一个名为 Itr 的实现了 Iterator 接口的内部类,通过 iterator() 方法可以获取该内部类的实例:

    public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
        transient Object[] elementData;
        
        private int size;
        
        protected transient int modCount = 0;
        
        public Iterator<E> iterator() { return new Itr(); }
        
        private class Itr implements Iterator<E> {
    
            int cursor = 0;
    
            int lastRet = -1;
    
            int expectedModCount = modCount;
    
            public boolean hasNext() {...}
    
            public E next() {...}
    
            public void remove() {...}
    
            final void checkForComodification() {...}
        }
    }
    
    • elementData

      集合中存储的所有元素,elementDate 的数组长度代表了 集合的容量

    • size

      集合包含元素的个数

    • modCount

      集合发生“结构性修改”的次数。对集合进行结构性修改的常见方式为调用add()、remove()等方法改变集合包含的元素数量。

      ArrayList<String> list = new ArrayList<String>(); // modCount == 0
      list.add("0"); // modCount == 1
      list.add("1"); // modCount == 2
      list.add("2"); // modCount == 3
      list.add("3"); // modCount == 4
      Iterator iterator = list.iterator();
      while (iterator.hasNext()) {
          String s = (String)iterator.next();
          if (!"2".equals(s)) continue;
          list.remove(s); // modCount == 5
      }
      

      就本文分析的代码而言,创建 ArrayList 实例时,modCount 的初值为0,即尚未对集合进行任何修改,在调用了4次 add() 方法后,modCount的值就累加到了4。

    • cursor

      当前遍历到的集合元素的下标。

    • lastRet

      上一次遍历到的集合元素的下标。

    • expectedModCount

      在只有当前迭代器对集合进行“结构性修改”的情况下,集合应该具有的结构性修改次数。

    • checkForComodification()

      检查集合是否发生“并发修改”。所有并发修改,指的是除了当前迭代器,外界也对集合进行了修改。由于 ArrayList 没有对并发访问进行控制,因此并发修改将使得当前迭代器后续的行为变得不可控。

      就 Itr 而言,它通过比较 modCount 和 expectedModCount 来判断是否还有元素尚未遍历。

      final void checkForComodification() {
          if (modCount != expectedModCount)
              throw new ConcurrentModificationException();
      }
      
    • hasNext()1111

      判断是否还有元素尚未遍历。就 Itr 而言,它通过比较 cusor 和 size 是否相等来判断是否还有元素尚未遍历。

      public boolean hasNext() {
          return cursor != size;
      }
      
    • next()

      向后遍历一个集合元素。

      public E next() {
          checkForComodification();
          
          int i = cursor;
      	if (i >= size)
      		throw new NoSuchElementException();
          
      	Object[] elementData = ArrayList.this.elementData;
      	if (i >= elementData.length)
      		throw new ConcurrentModificationException();
          
      	cursor = i + 1;
      	return (E) elementData[lastRet = i];
      }
      
    • remove()

      从集合中移除上一次调用 next() 方法遍历到的元素。

      public void remove() {
          // 每调用一次 next() 方法 最多只能调用一次 remove() 方法。
          if (lastRet < 0)
              throw new IllegalStateException();    
          
          checkForComodification();
      
          try {
              ArrayList.this.remove(lastRet);  // 从集合中移除元素,remove方法会更新modCount的值。
              cursor = lastRet;
              lastRet = -1;                    // 每调用一次 next() 方法 最多只能调用一次 remove() 方法。
              expectedModCount = modCount;     // 记录新的modCount。
          } catch (IndexOutOfBoundsException ex) {
              throw new ConcurrentModificationException();
          }
      }
      
    条件语句 代码序列 执行结果
    "0".equals(s) hashNext(): true
    cusor = 0,size = 4

    next(): “0”
    checkForComodification(): OK
    modCount = 4,expectedModCount = 4
    cusor = 1

    ”0”.equals(“0”): true
    remove(): OK
    modCount = 5,size = 3

    hashNext(): true
    cusor = 1,size = 3

    next(): Exception
    checkForComodification(): Exception
    modCount = 5,expectedModCount = 4
    java.util.ConcurrentModificationException
    "1".equals(s) java.util.ConcurrentModificationException
    "2".equals(s) hashNext(): true
    cusor = 2,size = 4

    next(): “2”
    checkForComodification(): OK
    modCount = 4,expectedModCount = 4
    cusor = 3

    ”2”.equals(“2”): true
    remove(): OK
    modCount = 5,size = 3

    hashNext(): false
    cusor = 3,size = 3

    正常执行
    "3".equals(s) hashNext(): true
    cusor = 3,size = 4

    next(): “3”
    checkForComodification(): OK
    modCount = 4,expectedModCount = 4
    cusor = 4

    ”3”.equals(“3”): true
    remove(): OK
    modCount = 5,size = 3

    hashNext(): true
    cusor = 4,size = 3

    next(): Exception
    checkForComodification(): Exception
    modCount = 5,expectedModCount = 4
    java.util.ConcurrentModificationException

    上面4个条件语句都在迭代器外部对集合进行了结构性修改,不同之处在于,"2".equals(s) 条件语句对集合进行结构行修改以后,就停止了在迭代器中修改集合,这就避免了冲突的发生,所以没有抛出异常。

    附件:

    完整源码:

    public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
        /**
         * The array buffer into which the elements of the ArrayList are stored.
         * The capacity of the ArrayList is the length of this array buffer. Any
         * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
         * will be expanded to DEFAULT_CAPACITY when the first element is added.
         */
        transient Object[] elementData; // non-private to simplify nested class access
        
        /**
         * The size of the ArrayList (the number of elements it contains).
         *
         * @serial
         */
         private int size;
        
        /**
         * The number of times this list has been <i>structurally modified</i>.
         * Structural modifications are those that change the size of the
         * list, or otherwise perturb it in such a fashion that iterations in
         * progress may yield incorrect results.
         *
         * <p>This field is used by the iterator and list iterator implementation
         * returned by the {@code iterator} and {@code listIterator} methods.
         * If the value of this field changes unexpectedly, the iterator (or list
         * iterator) will throw a {@code ConcurrentModificationException} in
         * response to the {@code next}, {@code remove}, {@code previous},
         * {@code set} or {@code add} operations.  This provides
         * <i>fail-fast</i> behavior, rather than non-deterministic behavior in
         * the face of concurrent modification during iteration.
         *
         * <p><b>Use of this field by subclasses is optional.</b> If a subclass
         * wishes to provide fail-fast iterators (and list iterators), then it
         * merely has to increment this field in its {@code add(int, E)} and
         * {@code remove(int)} methods (and any other methods that it overrides
         * that result in structural modifications to the list).  A single call to
         * {@code add(int, E)} or {@code remove(int)} must add no more than
         * one to this field, or the iterators (and list iterators) will throw
         * bogus {@code ConcurrentModificationExceptions}.  If an implementation
         * does not wish to provide fail-fast iterators, this field may be
         * ignored.
         */
        protected transient int modCount = 0;
        
        public Iterator<E> iterator() {
            return new Itr();
        }
        
        /**
         * An optimized version of AbstractList.Itr
         */
        private class Itr implements Iterator<E> {
            int cursor;       // index of next element to return
            int lastRet = -1; // index of last element returned; -1 if no such
            int expectedModCount = modCount;
    
            Itr() {}
    
            public boolean hasNext() {
                return cursor != size;
            }
    
            @SuppressWarnings("unchecked")
            public E next() {
                checkForComodification();
                int i = cursor;
                if (i >= size)
                    throw new NoSuchElementException();
                Object[] elementData = ArrayList.this.elementData;
                if (i >= elementData.length)
                    throw new ConcurrentModificationException();
                cursor = i + 1;
                return (E) elementData[lastRet = i];
            }
    
            public void remove() {
                if (lastRet < 0)
                    throw new IllegalStateException();
                checkForComodification();
    
                try {
                    ArrayList.this.remove(lastRet);
                    cursor = lastRet;
                    lastRet = -1;
                    expectedModCount = modCount;
                } catch (IndexOutOfBoundsException ex) {
                    throw new ConcurrentModificationException();
                }
            }
    
            @Override
            @SuppressWarnings("unchecked")
            public void forEachRemaining(Consumer<? super E> consumer) {
                Objects.requireNonNull(consumer);
                final int size = ArrayList.this.size;
                int i = cursor;
                if (i >= size) {
                    return;
                }
                final Object[] elementData = ArrayList.this.elementData;
                if (i >= elementData.length) {
                    throw new ConcurrentModificationException();
                }
                while (i != size && modCount == expectedModCount) {
                    consumer.accept((E) elementData[i++]);
                }
                // update once at end of iteration to reduce heap write traffic
                cursor = i;
                lastRet = i - 1;
                checkForComodification();
            }
    
            final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
        }
        
    }
    
  • 相关阅读:
    对称加密算法在C#中的踩坑日常
    php与Git下基于webhook的自动化部署
    1024程序员节阿里谜题解析
    一次ajax请求导致status为canceled的原因小记
    LigerUI下拉选择列表LigerComboBox中tree的节点初始化默认选中的问题
    linux环境下安装PHP扩展swoole
    Memcache PHP 使用笔记
    VS生成事件执行XCOPY时出现Invalid num of parameters的解决方案
    mysql表的完整性约束
    mysql支持的数据类型
  • 原文地址:https://www.cnblogs.com/XiaoZhengYu/p/14238466.html
Copyright © 2011-2022 走看看