我有一个单线程应用程序,它迭代一个巨大的树结构,其中子项存储在列表中。迭代器总是在不可修改的列表上运行:
public List<HierarchyNode> getChildren() {
return Collections.unmodifiableList(children);
}
在某些时候我仍然得到一个ConcurrentModificationException,我认为在不可修改的List中这是不可能的?迭代是使用访问者完成的...任何想法如何可能?
编辑:唯一可以修改此列表的人是持有列表的类的构造函数:
private final List<HierarchyNode> children;
也许这与thetree的内存使用量相当大(> 4GB)有关?
跟踪:
Testcase: testParserSingleFile(General.NetlistBuilder): Caused an ERROR
null
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:819)
at java.util.ArrayList$Itr.next(ArrayList.java:791)
at java.util.Collections$UnmodifiableCollection$1.next(Collections.java:1067)
at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitArchitectureNode(HierarchyNodeVisitorImplementation.java:20)
at com.bevm.semantics.netlist.NetlistBuilder.visitArchitectureNode(NetlistBuilder.java:40)
at com.bevm.hierarchy.ArchitectureNode.accept(ArchitectureNode.java:25)
at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitEntityNode(HierarchyNodeVisitorImplementation.java:33)
at com.bevm.semantics.netlist.NetlistBuilder.visitEntityNode(NetlistBuilder.java:33)
at com.bevm.hierarchy.EntityNode.accept(EntityNode.java:33)
at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitComponentNode(HierarchyNodeVisitorImplementation.java:27)
at com.bevm.hierarchy.ComponentNode.accept(ComponentNode.java:25)
at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitInstanceNode(HierarchyNodeVisitorImplementation.java:45)
at com.bevm.semantics.netlist.NetlistBuilder.visitInstanceNode(NetlistBuilder.java:85)
at com.bevm.hierarchy.InstanceNode.accept(InstanceNode.java:89)
at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitArchitectureNode(HierarchyNodeVisitorImplementation.java:21)
at com.bevm.semantics.netlist.NetlistBuilder.visitArchitectureNode(NetlistBuilder.java:40)
at com.bevm.hierarchy.ArchitectureNode.accept(ArchitectureNode.java:25)
at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitEntityNode(HierarchyNodeVisitorImplementation.java:33)
at com.bevm.semantics.netlist.NetlistBuilder.visitEntityNode(NetlistBuilder.java:33)
at com.bevm.hierarchy.EntityNode.accept(EntityNode.java:33)
at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitComponentNode(HierarchyNodeVisitorImplementation.java:27)
at com.bevm.hierarchy.ComponentNode.accept(ComponentNode.java:25)
at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitInstanceNode(HierarchyNodeVisitorImplementation.java:45)
at com.bevm.semantics.netlist.NetlistBuilder.visitInstanceNode(NetlistBuilder.java:85)
at com.bevm.hierarchy.InstanceNode.accept(InstanceNode.java:89)
at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitArchitectureNode(HierarchyNodeVisitorImplementation.java:21)
at com.bevm.semantics.netlist.NetlistBuilder.visitArchitectureNode(NetlistBuilder.java:40)
at com.bevm.hierarchy.ArchitectureNode.accept(ArchitectureNode.java:25)
at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitEntityNode(HierarchyNodeVisitorImplementation.java:33)
at com.bevm.semantics.netlist.NetlistBuilder.visitEntityNode(NetlistBuilder.java:33)
at com.bevm.hierarchy.EntityNode.accept(EntityNode.java:33)
at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitComponentNode(HierarchyNodeVisitorImplementation.java:27)
at com.bevm.hierarchy.ComponentNode.accept(ComponentNode.java:25)
at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitInstanceNode(HierarchyNodeVisitorImplementation.java:45)
at com.bevm.semantics.netlist.NetlistBuilder.visitInstanceNode(NetlistBuilder.java:85)
at com.bevm.hierarchy.InstanceNode.accept(InstanceNode.java:89)
at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitGenerateNode(HierarchyNodeVisitorImplementation.java:39)
at com.bevm.semantics.netlist.NetlistBuilder.visitGenerateNode(NetlistBuilder.java:79)
at com.bevm.hierarchy.GenerateNode.accept(GenerateNode.java:27)
at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitArchitectureNode(HierarchyNodeVisitorImplementation.java:21)
at com.bevm.semantics.netlist.NetlistBuilder.visitArchitectureNode(NetlistBuilder.java:40)
at com.bevm.hierarchy.ArchitectureNode.accept(ArchitectureNode.java:25)
at com.bevm.hierarchy.HierarchyNodeVisitorImplementation.visitEntityNode(HierarchyNodeVisitorImplementation.java:33)
at com.bevm.semantics.netlist.NetlistBuilder.visitEntityNode(NetlistBuilder.java:33)
at com.bevm.hierarchy.EntityNode.accept(EntityNode.java:33)
at com.beckhoff.vmagic.hierarchy.HierarchyNodeVisitorImplementation.visit(HierarchyNodeVisitorImplementation.java:15)
at General.NetlistBuilder.testParserSingleFile(NetlistBuilder.java:125)
答案 0 :(得分:10)
因此Collection.unmodifiableList
并非真正的线程安全。这是因为它创建了基础List
的不可修改的视图。但是,如果在迭代视图时修改了基础List
,您将获得CME。请记住,CME不需要由单独的线程引起。如果我执行以下操作,我将获得CME:
for (String e : myList){
myList.remove(5); // throws CME
}
更好的选择是Guava的ImmutableList,它创建了传递列表的不可变副本。
由于评论的澄清,请将发布的代码替换为:
public List<HierarchyNode> getChildren() {
return ImmutableList.copyOf(children);
}
从此方法返回的List
保证永远不会抛出CME。
更新
如果您仍在尝试弄清楚代码中的原因,请考虑以下事项:
children
的列表(我指的是传递给List
的可修改的unmodifiableList
)?getChildren
的任何类是否会导致以可能更新列表的方式调用包含可修改列表的类?Iterator
实例?答案 1 :(得分:2)
只有一个可以修改此列表的人是持有它的类的构造函数
在这种情况下,您根本不需要修改它。我建议你让它永远不变,你不会得到这个问题。
在你的构造函数中,你可以做到
List<HierarchyNode> children = new ArrayList<>();
// create/modify collection.
this.children = Collections.immutableList(children);
这简化了您的方法
public List<HierarchyNode> getChildren() {
return children;
}
如果您进行了这些更改,我怀疑您会找到修改集合的位置。
问题是Collections.unmodifiableList()
阻止使用此方法返回的引用进行修改。它不会阻止修改您正在包装的集合。
List<String> words = new ArrayList<String>();
words.add("hello");
words.add("world");
List<String> unmodifiable = Collections.unmodifiableList(words);
List<String> copy = new ArrayList<String>(words);
System.out.println("Before modification");
System.out.println("words: " + words);
System.out.println("unmodifiable: " + unmodifiable);
System.out.println("copy: " + copy);
words.remove("hello");
words.add("hi");
System.out.println("\nAfter modification");
System.out.println("words: " + words);
System.out.println("unmodifiable: " + unmodifiable);
System.out.println("copy: " + copy);
打印
Before modification
words: [hello, world]
unmodifiable: [hello, world]
copy: [hello, world]
After modification
words: [world, hi]
unmodifiable: [world, hi]
copy: [hello, world]
没有添加新库的解决方案是在返回之前复制该集合。
答案 2 :(得分:1)
您可能正在尝试修改里面的迭代列表,如下所示:
Iterator iter = list.iterator();
while (iter.hasNext()) {
if (someCondition)
list.remove(someObject);
}
相反,这是你应该做的:
for (int i = 0; i < list.size(); i++) {
if (someCondition)
list.remove(i--);
}
如果您的逻辑允许,您可以使用CopyOnWriteArrayList。每次修改时,此实现都会为您提供列表的全新副本。这对于您执行大量迭代但很少修改列表以及给定迭代不需要反映列表的最新修改的情况非常有用。我在调度程序/订阅者应用程序中经常使用它,并且它在性能方面表现很好。
答案 3 :(得分:0)
您无法修改集合,然后在其上使用现有迭代器 - 您是否尝试在某处修改列表(此处我的意思是原始列表)?
来自ConcurrentModificationException:
请注意,此异常并不总是表示对象具有 被另一个线程同时修改。
答案 4 :(得分:0)
很抱歉,大多数人都是对的,简单的答案是列表不是真正不可修改的...同一类的所有对象都不使用getChildren()方法,而是直接访问私有成员。因此,我将不得不在代码中继续搜索...您的评论将有所帮助