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 aCollections.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));
}
}