1

我有一个单线程应用程序,它遍历一个巨大的树结构,其中子项存储在一个列表中。迭代器总是在一个不可修改的列表上运行:

public List<HierarchyNode> getChildren() {  
        return Collections.unmodifiableList(children);  
}  

我仍然在某个时候得到 ConcurrentModificationException,我认为这在不可修改的列表中是不可能的?迭代是使用访问者完成的......任何想法这怎么可能?

编辑:唯一可以修改此列表的是持有该列表的类的构造函数:

private final List<HierarchyNode> children;

也许这与树的内存使用量相当大(> 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)
4

5 回答 5

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。

更新:

如果您仍在尝试找出代码中出现这种情况的原因,请考虑以下事项:

  1. 可以修改 EVER 的列表children(我指的List是传递给的可修改的unmodifiableList)吗?
  2. 是否有任何调用的类getChildren会导致以可能更新列表的方式调用包含可修改列表的类?
  3. 或者是否有任何类获​​得了一个Iterator多次使用的实例?

不可变列表

于 2012-11-05T13:03:07.600 回答
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]

不添加新库的解决方案是在返回之前复制集合。

于 2012-11-05T13:10:00.763 回答
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。每次修改列表时,此实现都会为您提供一个新的列表副本。这对于您对列表进行大量迭代但修改很少的情况以及给定迭代不需要反映对列表的最近修改的情况很有用。我在调度程序/订阅者应用程序中使用了很多,它在性能方面表现出色。

于 2012-11-05T13:25:17.730 回答
0

您不能修改 Collection 然后在其上使用现有的迭代器 - 您是否试图在某处修改列表(这里我的意思也是原始列表)?

ConcurrentModificationException

请注意,此异常并不总是表示对象已被不同的线程同时修改。

于 2012-11-05T13:04:02.093 回答
0

抱歉,你们中的大多数人是对的,简单的答案是列表并非真的不可修改......同一类的所有对象都不使用 getChildren() 方法,而是直接访问私有成员。所以我将不得不继续在代码中搜索......你的评论会有所帮助

于 2012-11-05T14:02:06.257 回答