【问题标题】:Modify a list while it is being iterating在迭代时修改列表
【发布时间】:2014-09-17 00:13:45
【问题描述】:

当我测试自己的答案以获取 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);
    }
}

【问题讨论】:

  • @TheNewIdiot:相关问题中的代码已经完成。
  • 如果你可以让这个问题自成一体,那真的很有帮助。我怀疑答案很简单,就是您在不同线程之间存在竞争条件。它正在完成添加的第二个和第三个项目之间的迭代。
  • @JonSkeet 他正在谈论他发布的代码作为我想的答案。
  • @TheNewIdiot:不,我刚刚发现了帖子的最后一行,它指的是使用增强的 for 循环的代码。当 OP 尝试 add 项目时,使用 iterator.remove() 将无济于事......并在不同的线程中这样做。 (这不是正常的“在循环中修改”。)
  • 我不明白你的谈话是关于什么的 ;) 无论如何,代码已添加。只有一个抛出异常。如果我只在列表中添加三个项目,它就不会再扔了。

标签: java iteration concurrentmodification


【解决方案1】:

您尝试重现的行为高度依赖时间。

当且仅当两个线程在修改列表时恰好在时间上重叠时,您会得到异常。

否则,您不会得到异常。

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


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

  • 三个项目均已打印
  • 其中一些已打印并引发异常

Jon Skeet 的回答指出了打印的元素少于三个的情况,无一例外,这意味着答案是


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

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

【讨论】:

  • 如果我理解正确,输出应该是打印的三个项目或其中的一些项目和一个异常,无论如何。我不认为数据竞赛在这里很重要,对吗?
  • @Rad,不一定。您必须考虑其他线程代码与主线程代码的所有可能交错,才能得出任何结论。为此,您必须查看 ArrayList 的迭代器的实现。
  • 这可能是正确的问题:迭代器实现。尽管如此,我希望在调用 next() 时有两种可能性:列表未修改或已修改。如果是,则预期异常。如果未修改,则应打印第三项。
  • @Rad,假设迭代器的next() 方法对于读取值、检查并发修改和抛出异常这三个操作是原子的,您的结论在逻辑上是合理的。
  • 我确实假设(不完全是你所说的,无论如何)。否则在这种情况下我无法计算异常。也许我太贪心了;)请您更新答案,以便我接受。
【解决方案2】:

从根本上说,您在一个线程中修改列表,同时在另一个线程中对其进行迭代,而您使用的列表实现支持。

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

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

【讨论】:

  • 我认为他的问题更多的是“为什么我有时会很幸运”而不是“为什么会发生异常?”
  • @merlin2011:这就是为什么我有一段关于“如果你很幸运”的原因
  • 很公平。我发表评论只是因为您打开时试图解释为什么会发生异常。 :)
  • @merlin2011:是的 - 尚不清楚 OP 是否完全理解这部分......
  • @Rad:当它尝试打印第 4 个项目时,只有 3 个项目。据我所知,它不是一个守护线程,所以它应该到最后。尝试在循环后添加System.out.println("Finished");
猜你喜欢
  • 2017-12-05
  • 2019-04-04
  • 1970-01-01
  • 2018-10-12
  • 2020-09-07
  • 2020-07-28
  • 1970-01-01
相关资源
最近更新 更多