Android|Java 开发中的那些坑 ConcurrentModificationException

16 篇文章 0 订阅

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

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 ()Z
Iterator.next ()Ljava/lang/Object;
Iterator.remove ()V

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

Iterator.hasNext ()Z
Iterator.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 ()Z
Iterator.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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值