Java 这是潜在的ConcurrentModificationException吗?我的解决方案正确吗?
我在一个项目中,有一个类如下Java 这是潜在的ConcurrentModificationException吗?我的解决方案正确吗?,java,multithreading,Java,Multithreading,我在一个项目中,有一个类如下 class Container{ List<Container> dependencies; //... other members and methods. addDependent(Container con){ for(Container c : dependencies){ if(c.id == con.id) return; dependencies.add(c); } } } 类容
class Container{
List<Container> dependencies;
//... other members and methods.
addDependent(Container con){
for(Container c : dependencies){
if(c.id == con.id) return;
dependencies.add(c);
}
}
}
类容器{
列出依赖关系;
//……其他成员和方法。
addDependent(容器con){
for(容器c:依赖项){
if(c.id==con.id)返回;
添加(c);
}
}
}
我们计算一个容器列表,如下所示。请注意,列表是巨大的,每次迭代的计算都是复杂的(我在这里只保留相关性)
List=//已初始化;
用于(容器c:列表){
c、 依赖性(c);
//用于填充容器的其他属性的其他计算。
}
此代码段位于单个线程中,工作正常,只是速度很慢,只有列表大小的bcoz。现在,我尝试将列表分成四个子列表,在四个线程中运行相同的代码,并在完成后将它们重新组合在一起(使用了同步器CountdownLatch
)
现在我得到了ConcurrentModificationException
错误。在做了一些梳理之后,我使用一个ThreadLocal变量在第一个代码片段中包装“列表依赖项”,CME就消失了
然而,问题是,我不太清楚为什么首先会发生异常,我的解决方案是否正确,或者在将来某个时候它会再次爆发 修改正在迭代的任何集合(除非通过迭代器进行修改)将导致出现
ConcurrentModificationException
。然而,在看了你们的课程之后,我想提出一个替代方案
现在,您正在将所有对象存储在列表中
,并测试每个对象是否为成员。如果将此结构切换到一个集合
,该集合只强制每种类型的对象中的一种,可能会使代码更清晰
class Container{
Set<Container> dependencies = new HashSet<>();
public void addDependent(Container con) {
dependencies.add(c);
}
public boolean equals(Container c) {
return c.id = this.id;
}
public int hashCode() {
// etc
}
}
类容器{
Set dependencies=newhashset();
公共void addDependent(容器con){
添加(c);
}
公共布尔等于(容器c){
返回c.id=this.id;
}
公共int hashCode(){
//等
}
}
请注意,您必须在容器
对象上实现equals()
和hashCode()
,以便集
能够区分容器
。关于并发修改异常
此异常不一定在多线程代码中引发。当您在迭代集合时修改集合时会发生这种情况。即使在单线程应用程序中也会出现此异常。例如,在For-each循环中,如果删除或向列表中添加元素,则最终会得到一个ConcurrentModificationException
因此,向代码中添加同步并不一定能解决问题。一些替代方法包括制作要迭代的数据的副本,或者使用接受修改的迭代器(即ListIterator),或者使用带有快照迭代器的集合
显然,在多线程代码中,您仍然需要注意同步以避免进一步的问题
在上面的代码中,您正好遇到了这个问题。你写道:
for(容器c:依赖项){
if(c.id==con.id)返回;
添加(c);
}
这可能会修改for each循环中迭代下的相同集合,并且每次不满足if语句中计算的条件时,都可能会得到一个CME
让我举几个例子:
假设您希望在迭代集合时从集合中删除项。要避免出现ConcurrentModificationException
异常,您可以选择以下选项:
List<Book> books = new ArrayList<Book>();
books.add(new Book(new ISBN("0-201-63361-2")));
books.add(new Book(new ISBN("0-201-63361-3")));
books.add(new Book(new ISBN("0-201-63361-4")));
在多线程环境中,您可能会考虑在迭代之前创建集合的副本,这样,允许其他人修改原始集合而不影响迭代:
synchronized(this.books) {
List<Book> copyOfBooks = new ArrayList<Book>(this.books)
}
for(Book book : copyOfBooks) {
System.out.println(book);
}
synchronized(this.books){
List copyOfBooks=new ArrayList(this.books)
}
用于(书籍:书籍副本){
系统输出打印(图书);
}
可选地,您可以考虑使用快照迭代器使用其他类型的集合,如
addDependent(Container con){
for(Container c : dependencies){
if(c.id == con.id) return;
dependencies.add(c);
}
}
将c添加到每个不匹配项的依赖项中。例如,如果依赖项中容器中的现有id为(1、2、3、4、5),并且您使用id为6的容器调用addDependent,那么最终将得到(1、2、3、4、5、6、6、6、6、6、6)。此外,有了这个bug,您永远不会向依赖项添加任何内容,因为您只会在列表已经非空的情况下添加。您应该已将add移动到for循环之外:
addDependent(Container con){
for(Container c : dependencies){
if(c.id == con.id) return;
}
dependencies.add(c);
}
这本可以正确工作并消除CME,但对于大型依赖项列表,集合方法的性能会更好。是否有理由使用
列表而不是集合
?您正在修改正在迭代的相同集合。这导致了CME,甚至与多线程无关。如果在同步环境中使用此列表,则不能使用ArrayList
。必须执行以下操作之一:1)使用集合.synchronizedList()
方法,或2)使用其线程安全变量:CopyOnWriteArrayList
。作为@EdwinDalorzo
synchronized(this.books) {
List<Book> copyOfBooks = new ArrayList<Book>(this.books)
}
for(Book book : copyOfBooks) {
System.out.println(book);
}
addDependent(Container con){
for(Container c : dependencies){
if(c.id == con.id) return;
dependencies.add(c);
}
}
addDependent(Container con){
for(Container c : dependencies){
if(c.id == con.id) return;
}
dependencies.add(c);
}