Java 在列表迭代时修改列表

Java 在列表迭代时修改列表,java,iteration,concurrentmodification,Java,Iteration,Concurrentmodification,当我测试自己的答案以获得问题的输出时,我得到了给定列表内容的以下输出: // Add some strings into the list list.add("Item 1"); list.add("Item 2"); list.add("Item 3"); list.add("Item 4"); public class Main { public static void main(String[] args) throws InterruptedException {

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

// Add some strings into the list
list.add("Item 1");
list.add("Item 2");
list.add("Item 3");
list.add("Item 4");
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);
    }
}
输出:

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)
Item 1
Item 2
但如果使用以下列表:

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

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)
Item 1
Item 2
输出中没有异常,仅打印前两项。 有人能解释它为什么会这样吗?谢谢

注:代码为

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

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

// Add some strings into the list
list.add("Item 1");
list.add("Item 2");
list.add("Item 3");
list.add("Item 4");
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);
    }
}
公共类主{
公共静态void main(字符串[]args)引发InterruptedException
{
最终ArrayList=新ArrayList();
列表。添加(“第1项”);
列表。添加(“第2项”);
列表。添加(“第3项”);
列表。添加(“第4项”);
Thread Thread=新线程(new Runnable()
{
@凌驾
公开作废运行()
{
用于(字符串s:列表)
{
系统输出打印项次;
尝试
{
睡眠(1000);
}
捕捉(中断异常e)
{
e、 printStackTrace();
}
}
}
});
thread.start();
《睡眠》(2000年);
列表。删除(0);
}
}

您试图重现的行为高度依赖于时间

当且仅当修改列表时两个线程在时间上发生重叠时,才会出现异常

否则,您不会得到异常

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


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

  • 所有三项都已打印
  • 其中一些已打印,并引发异常
Jon Skeet的回答指出了一种情况,即打印的元素少于三个,没有例外,这意味着答案是


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

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


您试图复制的行为高度依赖于时间

当且仅当修改列表时两个线程在时间上发生重叠时,才会出现异常

否则,您不会得到异常

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


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

  • 所有三项都已打印
  • 其中一些已打印,并引发异常
Jon Skeet的回答指出了一种情况,即打印的元素少于三个,没有例外,这意味着答案是


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

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


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

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


如果您希望在多个线程中使用列表,并且其中至少有一个线程正在修改它,那么您应该使用支持该功能的实现,例如
copyonwritearlaylist

,基本上,您在一个线程中修改列表,而在另一个线程中对其进行迭代,您正在使用的列表实现不支持这一点

ArrayList
迭代器实现似乎只在调用
next()
时检测到无效修改,而在调用
hasNe时检测不到