Android|Java 开发中的那些坑 ConcurrentModificationException

来源:互联网 发布:卢武铉在韩国名声知乎 编辑:程序博客网 时间:2024/05/17 08:19

开发中会遇到很多问题,比如:

public static void foreach(List<String> names) {        for (String s : names) {            names.remove(s);        }    }

会抛出 ConcurrentModificationException异常,但是下面这种写法:

public static void iterator(List<String> names) {        Iterator<String> iterator = names.iterator();        while (iterator.hasNext()) {            iterator.next();            iterator.remove();        }    }

却不会抛出该异常!

我们看看这两个方法的字节码:

  // access flags 0x9  // signature (Ljava/util/List<Ljava/lang/String;>;)V  // declaration: void foreach(java.util.List<java.lang.String>)  public static foreach(Ljava/util/List;)V   L0    LINENUMBER 13 L0    ALOAD 0    INVOKEINTERFACE java/util/    Ljava/util/Iterator;    ASTORE 1   L1   FRAME APPEND [java/util/Iterator]    ALOAD 1    INVOKEINTERFACE java/util/Iterator.hasNext ()Z    IFEQ L2    ALOAD 1    INVOKEINTERFACE java/util/Iterator.next ()Ljava/lang/Object;    CHECKCAST java/lang/String    ASTORE 2   L3    LINENUMBER 14 L3    ALOAD 0    ALOAD 2    INVOKEINTERFACE java/util/List.remove (Ljava/lang/Object;)Z    POP   L4    LINENUMBER 15 L4    GOTO L1   L2    LINENUMBER 16 L2   FRAME CHOP 1    RETURN   L5    LOCALVARIABLE s Ljava/lang/String; L3 L4 2    LOCALVARIABLE names Ljava/util/List; L0 L5 0    // signature Ljava/util/List<Ljava/lang/String;>;    // declaration: java.util.List<java.lang.String>    MAXSTACK = 2    MAXLOCALS = 3
  // access flags 0x9  // signature (Ljava/util/List<Ljava/lang/String;>;)V  // declaration: void iterator(java.util.List<java.lang.String>)  public static iterator(Ljava/util/List;)V   L0    LINENUMBER 19 L0    ALOAD 0    INVOKEINTERFACE java/util/List.iterator ()Ljava/util/Iterator;    ASTORE 1   L1    LINENUMBER 20 L1   FRAME APPEND [java/util/Iterator]    ALOAD 1    INVOKEINTERFACE java/util/Iterator.hasNext ()Z    IFEQ L2   L3    LINENUMBER 21 L3    ALOAD 1    INVOKEINTERFACE java/util/Iterator.next ()Ljava/lang/Object;    POP   L4    LINENUMBER 22 L4    ALOAD 1    INVOKEINTERFACE java/util/Iterator.remove ()V    GOTO L1   L2    LINENUMBER 24 L2   FRAME SAME    RETURN   L5    LOCALVARIABLE names Ljava/util/List; L0 L5 0    // signature Ljava/util/List<Ljava/lang/String;>;    // declaration: java.util.List<java.lang.String>    LOCALVARIABLE iterator Ljava/util/Iterator; L1 L5 1    // signature Ljava/util/Iterator<Ljava/lang/String;>;    // declaration: java.util.Iterator<java.lang.String>    MAXSTACK = 1    MAXLOCALS = 2

我简化一下,把两个方法的字节码的关键部分贴出来:
foreach(List names):

 Iterator.hasNext ()Z Iterator.next () List.remove (Ljava/lang/Object;)Z

iterator(List names):

Iterator.hasNext ()ZIterator.next ()Ljava/lang/Object;Iterator.remove ()V

我们先来看正确的iterator(List names)的调用:

Iterator.hasNext ()ZIterator.next ()Ljava/lang/Object;Iterator.remove ()V

看看Iterator.next()和Iterator.remove的源码:

 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();            }        }

注意每次调用Iterator.next()和Iterator.remove()的时候都会调用:

checkForComodification()

而在该方法:

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

大家可以看到一旦出现 modCount != expectedModCount 就会抛出异常!

并且最关键的是每次调用Iterator.remove的时候,都会有:

     expectedModCount = modCount;

这就保证了每次调用checkForCommodification()时不会导致
expectedModCount != modCount的情况出现

那我们来说说foreach(List names)为什么导致异常抛出
foreach(List names)的关键字节码为:

Iterator.hasNext ()Iterator.next ()List.remove (Ljava/lang/Object;)Z

跟iterator(List names):

Iterator.hasNext ()ZIterator.next ()Ljava/lang/Object;Iterator.remove ()V

不同的是 List.remove (Ljava/lang/Object;)Z
这个调用会直接调用:

private void fastRemove(int index) {        modCount++;        int numMoved = size - index - 1;        if (numMoved > 0)            System.arraycopy(elementData, index+1, elementData, index,                             numMoved);        elementData[--size] = null; // clear to let GC do its work    }

注意这里的modCount++,但是并没有对expectedModCount++,这就导致了:
expectedModCount != modCount的情况出现
而在下一次Iterator.next()方法中调用会执行:

在Iterator.hasNext()方法中有如下调用:

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

这时异常发生了!

当我们显示的使用Iterator的时候,每次调用都会保证:

modCount = expectedModCount

所以不会发生问题,但是当我们使用增强的foreach循环的时候,我们如果直接调用 List.remove,就会破坏Iterator的循环逻辑,导致 modCount != expectedModCount,问题的根源就在这里,所以当我们使用增强的foreach循环的时候,不用List.remove,同样也不要调用List.add操作!

0 0
原创粉丝点击