【问题标题】:It does not throw exception ConcurrentModificationException [duplicate]它不会抛出异常 ConcurrentModificationException [重复]
【发布时间】:2014-08-24 17:48:12
【问题描述】:

我有下面的代码,我希望它会抛出一个ConcurrentModificationException,但它运行成功。为什么会这样?

public void fun(){
    List <Integer>lis = new ArrayList<Integer>();
    lis.add(1);
    lis.add(2);

    for(Integer st:lis){
        lis.remove(1);
        System.out.println(lis.size());
    }
}

public static void main(String[] args) {
    test t = new test();
    t.fun();
}

【问题讨论】:

  • 为什么会抛出这个错误? ArrayList.remove() 不会抛出那个错误,只是索引越界。
  • psssst!....你从来没有看到我对这个问题的回答:)

标签: java iterator


【解决方案1】:

List 上的 remove(int) 方法删除指定位置的元素。在开始循环之前,您的列表如下所示:

[1, 2]

然后你在列表上启动一个迭代器:

[1, 2]
 ^

您的for 循环然后删除位置 1 的元素,即数字 2:

[1]
 ^

迭代器在下一次隐含的hasNext() 调用中返回false,循环终止。

如果您向列表中添加更多元素,您将获得ConcurrentModificationException。然后隐含的next() 会抛出。

作为注释,来自 JCF 的 ArrayList 的 Javadoc:

请注意,无法保证迭代器的快速失败行为,因为一般来说,在存在不同步的并发修改的情况下无法做出任何硬保证。快速失败的迭代器会尽最大努力抛出ConcurrentModificationException。因此,编写一个依赖此异常来确保其正确性的程序是错误的:迭代器的快速失败行为应该只用于检测错误

这可能实际上是Oracle ArrayList 迭代器实现中的一个错误; hasNext()检查修改:

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

【讨论】:

    【解决方案2】:

    它不会抛出 ConcurrentModificationException,因为正如 vandale 所说,迭代器只检查 next() 上的共修改。下面是 ArrayList 返回的 Iterator 实例的一部分:

        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];
        }
    

    hasNext() 只是查看光标是否指向列表的最后一个索引。它不检查列表是否被修改。因此,您不会收到 ConcurrentModificationException,它只是停止迭代。

    【讨论】:

      【解决方案3】:

      如果您有 3 个类似的列表:

      lis.add(1);
      lis.add(2);
      lis.add(3);
      

      在您的情况下,您将获得 ConcurrentModificationException。 PS:这个我试过了!

      【讨论】:

        【解决方案4】:

        因为您没有删除 1,所以您正在删除 1 处的元素。(remove(int) vs remove(Object)

        迭代器只会在调用next()而不是hasNext()时检查修改,并且循环将在调用hasNext()后退出,因为你删除了2,列表只有一个长,因此退出.

        【讨论】:

        • 实际上,如果您在索引 0 处删除它也不会引发异常。请在发布之前测试您的答案。
        • @Ordous 同样的原理,循环会在检查列表是否被修改之前退出
        • 原理相同,但第一句话完全无关紧要,“因为”会让任何人读到它的实际原因。
        【解决方案5】:

        正如ArrayListConcurrentModificationException 所述:

        请注意,无法保证迭代器的快速失败行为,因为一般来说,在存在不同步的并发修改的情况下无法做出任何硬保证。快速失败的迭代器会尽最大努力抛出 ConcurrentModificationException。

        现在是ArrayList返回的Iterator的代码示例:

            public boolean hasNext() {
                return cursor != size;
            }
        
            public E next() {
                checkForComodification();
                <stuff>
                return <things>;
            }
        
            <more methods>
        
            final void checkForComodification() {
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
            }
        

        您可以清楚地看到,在 ArrayList 的情况下,“最大努力”是在调用 next() 时检查修改 987654328@。您的循环终止而没有第二次调用next(),因此也不例外。如果您有 3 个元素开始,或者添加一个元素,它将失败。另外值得注意的是,如果您使用反射修改数组列表而不更新modCount 变量(顽皮...),那么根本不会抛出异常。 modCount 也不是易失性的,这再次表明它只是尽力而为并且不能保证,因为迭代器可能无论如何都看不到最新值。

        【讨论】:

          【解决方案6】:

          在这个循环中:

             for(Integer st:lis){
                  lis.remove(1);
                  System.out.println(lis.size());
              }
          

          您只是不断地从矩阵中删除索引为 1 的元素,甚至不关心 st 中的内容。所以这个循环和每次迭代都会尝试删除索引为 1 的项目。并发修改会产生这个循环:

           for(Integer st:lis){
              lis.remove(st);
              System.out.println(lis.size());
          }
          

          【讨论】:

          • 这实际上是不正确的,因为他正在从索引 1 中删除项目。如果 Java 将 int 1 自动装箱为 Integer 1,它只会删除 1 的值。
          • 你是对的,我是愚蠢的。不敢相信这个答案被两次投票。我会努力解决的。
          【解决方案7】:

          列表中只有 2 个条目。因此,由于您要删除条目,因此循环只运行一次。

          如果列表被修改并且您再次尝试对其进行一些操作,则会抛出 ConcurrentModificationException。但是在对其进行任何操作之前,我们已经脱离了循环,因此也不例外。尝试在列表中添加另一个条目并运行您的程序,这将引发异常。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-02-12
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-02-22
            相关资源
            最近更新 更多