在迭代时修改列表

时间:2014-07-25 07:52:50

标签: java iteration concurrentmodification

当我测试自己的答案以获取this问题的输出时,我得到了给定列表内容的以下输出:

// Add some strings into the list
list.add("Item 1");
list.add("Item 2");
list.add("Item 3");
list.add("Item 4");

输出:

Item 1
Item 2
Exception in thread "Thread-0" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
    at java.util.ArrayList$Itr.next(ArrayList.java:831)
    at com.akefirad.tests.Main$1.run(Main.java:34)
    at java.lang.Thread.run(Thread.java:745)

但如果使用以下列表:

// Add some strings into the list
list.add("Item 1");
list.add("Item 2");
list.add("Item 3");

输出:

Item 1
Item 2

输出中没有例外,只会打印两个第一项。 谁能解释为什么它会像这样?感谢

注意:代码为here

编辑:我的问题是为什么我没有打印第三个项目(意思是列表被修改),而且没有例外。

编辑代码以产生异常,请注意列表内容:

public class Main {

    public static void main(String[] args) throws InterruptedException
    {
        final ArrayList<String> list = new ArrayList<String>();
        list.add("Item 1");
        list.add("Item 2");
        list.add("Item 3");
        list.add("Item 4");

        Thread thread = new Thread(new Runnable()
        {
            @Override
            public void run ()
            {
                for (String s : list)
                {
                    System.out.println(s);
                    try
                    {
                        Thread.sleep(1000);
                    }
                    catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread.start();

        Thread.sleep(2000);
        list.remove(0);
    }
}

2 个答案:

答案 0 :(得分:4)

您尝试重现的行为与时间有很大关系。

当且仅当两个线程在修改列表时碰巧重叠时才会出现异常。

否则,你不会得到例外。

使用Thread.sleep()无法可靠地强制两个线程之间的重叠,因为内核总是可以决定在它们唤醒后任意调度线程。


更新:OP想要知道是否必须发生以下两种情况之一:

  • 打印所有三个项目
  • 其中一些打印并抛出异常

Jon Skeet的答案指出了一个案例,其中打印的元素少于三个,没有例外,这意味着答案是


更一般地说,寻找ConcurrentModificationException 不是检测多个线程的可靠方法同时修改和读取对象。事实上,Exception的Javadoc非常具体地阐述了这一点。

  

请注意,通常情况下无法保证快速失败的行为   说话,在场的情况下不可能做出任何硬性保证   不同步的并发修改。失败快速操作投掷   ConcurrentModificationException是尽力而为的。因此,它   编写一个依赖于此异常的程序是错误的   它的正确性:应该只使用ConcurrentModificationException   检测错误。

答案 1 :(得分:2)

从根本上说,你在一个帖子中修改一个列表而在另一个帖子中迭代它,而你正在使用的列表实现支持它。

ArrayList迭代器实现似乎仅在next()的调用中检测到对hasNext()而非的调用的无效修改。因此,如果您在<{em> remove()调用之前进入循环的最后一次迭代,那么您将无法获得异常 - hasNext()将返回false }。另一方面,如果remove()发生在最后一次调用next()之前(并且如果在另一个线程上注意到 - 内存模型在这里发挥作用),那么你将得到例外。因此,例如,如果您将循环内睡眠更改为Thread.sleep(2500),那么您将在第二次迭代开始时获得异常,因为remove()调用将在它之前发生。

如果要在多个线程中使用列表并且至少其中一个正在修改它,则应使用支持该列表的实现,例如CopyOnWriteArrayList