Java 使用复制构造函数时并发修改列表

Java 使用复制构造函数时并发修改列表,java,concurrency,concurrentmodification,Java,Concurrency,Concurrentmodification,以下代码是否会导致ConcurrentModificationException或其他副作用 ArrayList<String> newList = new ArrayList<String>(list); ArrayList newList=新的ArrayList(list); 考虑到列表的大小非常大,当执行上述代码时,另一个线程正在同时修改列表。编辑: 我最初的回答是肯定的,但正如@JohnVint正确指出的,这不会是一个ConcurrentModificatio

以下代码是否会导致
ConcurrentModificationException
或其他副作用

ArrayList<String> newList = new ArrayList<String>(list);
ArrayList newList=新的ArrayList(list);
考虑到列表的大小非常大,当执行上述代码时,另一个线程正在同时修改列表。

编辑:

我最初的回答是肯定的,但正如@JohnVint正确指出的,这不会是一个
ConcurrentModificationException
,因为在幕后
ArrayList
正在使用
System.arrayCopy(…)
复制数组。请参阅末尾的代码片段

问题是,在执行此复制时,另一个线程正在对元素数组进行更改。您可能会得到
IndexOutOfBoundsException
、未初始化的数组值,甚至是某种本机内存访问异常,因为
System.arraycopy(…)
是在本机代码中完成的

在更新和复制期间,您需要在列表上同步,以防止这些竞争条件,并建立内存屏障,以确保支持
ArrayList
的元素数组适当地处于最新状态



public ArrayList(Collection您需要考虑在这里做什么。如果
list
的类不是线程安全的,您可以使用此代码和
newList
一起彻底销毁
list
——CME将是您的最小问题。(我建议一个不抛出CME的类,但在这种情况下,CME是一件好事。)注意:这段代码很难测试。在每次失败之间,你会得到0到10亿次无问题运行,失败可能是非常微妙的,尽管它们更可能是巨大的,超出了合理的解释

最快的修复方法是锁定
列表
。您要确保在使用它的任何地方都锁定它;您不是真正锁定列表,而是锁定从中访问它的代码块。您必须锁定所有访问。缺点是,在创建新列表时,您会阻止其他线程。这确实是一种方法.然而,如果,正如你所说,“名单非常庞大”,你可能会担心表现,所以我会继续

如果
newList
被视为不可变的,并且您在创建后经常使用它,那么这样做是值得的。现在许多代码可以同时读取
newList
,而不会出现问题,也不会担心不一致。但在最初创建时仍然存在障碍

下一步是制作
list
一个java.util.ConcurrentLinkedQueue(如果需要更高级的东西,可以设置一个并发映射)这个东西可以有一堆线程读取它,同时添加和删除更多的线程,而且它总是有效的。它可能不包含您认为它包含的内容,但迭代器不会进入无限循环(如果
list
是java.util.LinkedList,可能会发生这种情况)。这样就可以在一个内核上创建
newList
,而另一个线程在另一个内核上工作

缺点:如果
list
是ArrayList,您可能会发现切换到并发类需要一些工作。并发类使用更多内存,通常比ArrayList慢。更重要的是,
list
的内容可能不一致。(实际上,您已经遇到了这个问题。)您可能会同时在另一个线程中添加或删除条目A和B,并期望两者都或两者都不在
newList
中,而事实上,只有一个条目在那里很容易,迭代器在添加或删除一个条目之后,但在另一个条目之前通过。(单核机器没有那么多问题。)但是如果
list
已经被认为处于一个恒定的、无序的通量中,这可能正是你想要的

另一个不同的副作用:你必须小心使用大型数组和使用它们的东西(如ArrayList和HashTable)。当你删除条目时,它们不会占用更少的空间,因此你可能会得到一堆大型数组,其中的数据很少,占用你大部分内存

更糟糕的是,当您添加条目时,它们会释放旧数组并分配新的、更大的数组, 这会导致对可用内存进行碎片整理。也就是说,可用内存大部分是从旧数组中删除的块,没有一个大到足以用于下一次分配。垃圾收集器将尝试对所有这些进行碎片整理,但这需要大量工作,GC倾向于抛出内存不足的异常,而不是花时间重新启动更改空闲块,这样它就可以获得您刚才请求的最大内存块。这样,当您的内存只有10%在使用时,就会出现内存不足错误


数组是速度最快的东西,但是你需要小心使用大数组。注意每个分配和释放。给它们一个合适的初始大小,这样它们就不会重新分配空间。(假装你是C程序员。)善待你的GC。如果你必须很好地创建和释放和调整大列表,请考虑使用链接类:Link Kelist、TreMeAP、CONCURNCE LIKEDE队列等等。它们只使用少量的内存,GC喜欢它们。

< P>我创建了一些代码来测试什么“Gray”。从数组列表中移除复制构造函数时使用NU导致NU。创建的列表中的所有元素。您可以看到这一点,因为在以下代码段中,错误条目的数量不断增加:

public static void main(String[] args) {

    final int n = 1000000;
    final int m = 100000;
    final ArrayList<String> strings = new ArrayList<String>(n);

    for(int i=0; i<n; i++) {
        strings.add(new String("abc"));
    }


    Thread creatorThread = new Thread(new Runnable() {
        @Override
        public void run() {
            ArrayList<String> stringsCme = new ArrayList<String>(strings);
            int wrongEntries = 0;
            for(int i=0; i<m; i++) {
                stringsCme = new ArrayList<String>(strings);

                for(String s : stringsCme) {
                    if(s == null || !s.equals("abc")) {
                        //System.out.println("Wrong entry: " + s);
                        wrongEntries++;
                    }
                }

                if(i % 100 == 0)
                    System.out.println("i = " + i + "\t list: " + stringsCme.size() + ", #wrong entries: " + wrongEntries);
            }

            System.out.println("#Wrong entries: " + wrongEntries);
        }
    });
    creatorThread.start();

    for(int i=0; i<m; i++) {
        strings.remove(MathUtils.random(strings.size()-1));
    }
}
publicstaticvoidmain(字符串[]args){
最终整数n=1000000;
最终整数m=100000;
最终ArrayList字符串=新ArrayList(n);

对于(int i=0;iEven a
Collections.synchronizedList
可以通过迭代获得CME,除非它已被
synchronized(list){}
我实际上无法实现列表大小1000000的CME。在数组列表的特殊情况下,支持的数组是copi
public static void main(String[] args) {

    final int n = 1000000;
    final int m = 100000;
    final ArrayList<String> strings = new ArrayList<String>(n);

    for(int i=0; i<n; i++) {
        strings.add(new String("abc"));
    }


    Thread creatorThread = new Thread(new Runnable() {
        @Override
        public void run() {
            ArrayList<String> stringsCme = new ArrayList<String>(strings);
            int wrongEntries = 0;
            for(int i=0; i<m; i++) {
                stringsCme = new ArrayList<String>(strings);

                for(String s : stringsCme) {
                    if(s == null || !s.equals("abc")) {
                        //System.out.println("Wrong entry: " + s);
                        wrongEntries++;
                    }
                }

                if(i % 100 == 0)
                    System.out.println("i = " + i + "\t list: " + stringsCme.size() + ", #wrong entries: " + wrongEntries);
            }

            System.out.println("#Wrong entries: " + wrongEntries);
        }
    });
    creatorThread.start();

    for(int i=0; i<m; i++) {
        strings.remove(MathUtils.random(strings.size()-1));
    }
}